> ## Documentation Index
> Fetch the complete documentation index at: https://api.smartlead.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Get Campaign Analytics by Date Range

> Retrieve campaign analytics for a specific date range

<Note>
  Analyze campaign performance over a specific time period. Essential for tracking improvements and identifying trends.
</Note>

## Path Parameters

<ParamField path="campaign_id" type="number" required>
  Campaign ID
</ParamField>

## Query Parameters

<ParamField query="api_key" type="string" required>
  Your SmartLead API key
</ParamField>

<ParamField query="start_date" type="string" required>
  Start date in `YYYY-MM-DD` format. Interpreted as the start of that day.
</ParamField>

<ParamField query="end_date" type="string" required>
  End date in `YYYY-MM-DD` format. Interpreted as the end of that day.
</ParamField>

<ParamField query="time_zone" type="string" default="UTC">
  IANA time zone used to resolve the start and end of each day, for example `America/New_York`. Defaults to `UTC`.
</ParamField>

<Warning>
  The range between `start_date` and `end_date` must be **30 days or less**. A wider range returns `400 Bad Request`.
</Warning>

## Response Fields

<ResponseField name="id" type="number">
  Campaign ID
</ResponseField>

<ResponseField name="user_id" type="number">
  ID of the user who owns the campaign
</ResponseField>

<ResponseField name="created_at" type="string">
  Campaign creation timestamp
</ResponseField>

<ResponseField name="status" type="string">
  Campaign status, for example `ACTIVE`, `PAUSED`, `COMPLETED`, `ARCHIVED`
</ResponseField>

<ResponseField name="name" type="string">
  Campaign name
</ResponseField>

<ResponseField name="start_date" type="string">
  The `start_date` you supplied, echoed back
</ResponseField>

<ResponseField name="end_date" type="string">
  The `end_date` you supplied, echoed back
</ResponseField>

<ResponseField name="sent_count" type="string">
  Emails sent in the date range
</ResponseField>

<ResponseField name="unique_sent_count" type="string">
  Leads that received a first-sequence email in the date range
</ResponseField>

<ResponseField name="open_count" type="string">
  Opens in the date range
</ResponseField>

<ResponseField name="unique_open_count" type="string">
  Distinct leads who opened in the date range
</ResponseField>

<ResponseField name="click_count" type="string">
  Clicks in the date range
</ResponseField>

<ResponseField name="unique_click_count" type="string">
  Distinct leads who clicked in the date range
</ResponseField>

<ResponseField name="reply_count" type="string">
  Replies in the date range, excluding any reply explicitly marked as ignored
</ResponseField>

<ResponseField name="total_reply_count" type="string">
  Every reply received in the date range, with no exclusions. See [Separating out-of-office replies](#separating-out-of-office-replies).
</ResponseField>

<ResponseField name="non_ooo_reply_count" type="string">
  Replies in the date range from leads **not** categorised as out-of-office. See [Separating out-of-office replies](#separating-out-of-office-replies).
</ResponseField>

<ResponseField name="block_count" type="string">
  Emails blocked in the date range
</ResponseField>

<ResponseField name="bounce_count" type="string">
  Bounced emails in the date range
</ResponseField>

<ResponseField name="unsubscribed_count" type="string">
  Unsubscribes in the date range
</ResponseField>

<ResponseField name="total_count" type="string">
  Total campaign records not in a `STOPPED` state. This is a campaign-lifetime figure and is **not** limited to the date range.
</ResponseField>

<ResponseField name="drafted_count" type="string">
  Campaign records in a `DRAFTED` state. This is a campaign-lifetime figure and is **not** limited to the date range.
</ResponseField>

<Note>
  All count fields are returned as strings.
</Note>

## Separating out-of-office replies

Automated out-of-office replies are counted as replies. Two fields let you separate them from genuine ones:

| Field | What it counts |
| - | - |
| `total_reply_count` | Every reply, with no exclusions |
| `non_ooo_reply_count` | Replies from leads not categorised as out-of-office |

```
ooo_replies = total_reply_count - non_ooo_reply_count
```

Both come from reply categorisation alone, so they do **not** depend on the per-campaign *ignore out-of-office* setting. You get the split whether or not that toggle is on — which matters, because turning it on is not retroactive.

### Worked example

A campaign receives 6 replies. The *ignore out-of-office* toggle is **off**, which is the default.

| # | What arrived | Category | Flagged ignored | `total_reply_count` | `reply_count` | `non_ooo_reply_count` |
| - | - | - | - | :-: | :-: | :-: |
| 1 | "Sounds interesting" | Interested | no | ✅ | ✅ | ✅ |
| 2 | "Not for us" | Not interested | no | ✅ | ✅ | ✅ |
| 3 | Auto-reply, back Monday | Out of office | no | ✅ | ✅ | — |
| 4 | Auto-reply, on leave | Out of office | no | ✅ | ✅ | — |
| 5 | Auto-reply, parental leave | Out of office | no | ✅ | ✅ | — |
| 6 | Forwarded by a colleague | Interested | yes, sender is not the lead | ✅ | — | ✅ |
| | | | **Total** | **6** | **5** | **3** |

Reading it:

* `reply_count` is **5**, but 3 of those are automated out-of-office replies.
* `non_ooo_reply_count` is **3**, so you can report on genuine replies without the auto-replies skewing the number.
* Out-of-office replies are `6 - 3 = 3`.
* Row 6 counts towards `non_ooo_reply_count` but not `reply_count`, because it was flagged as ignored for a reason unrelated to out-of-office.

### When the toggle is already on

Same idea, but out-of-office replies are also flagged as ignored, so they drop out of `reply_count` too.

| # | What arrived | Category | Flagged ignored | `total_reply_count` | `reply_count` | `non_ooo_reply_count` |
| - | - | - | - | :-: | :-: | :-: |
| 1 | "Happy to chat" | Interested | no | ✅ | ✅ | ✅ |
| 2 | Auto-reply, back Monday | Out of office | yes, by the toggle | ✅ | — | — |
| 3 | Forwarded by a colleague | Interested | yes, sender is not the lead | ✅ | — | ✅ |
| 4 | Spam you ignored by hand | uncategorised | yes, manually | ✅ | — | ✅ |
| | | | **Total** | **4** | **1** | **3** |

Here `non_ooo_reply_count` (3) is **higher** than `reply_count` (1). That is correct — see the warning below.

<Warning>
  **`non_ooo_reply_count` can be higher than `reply_count`.** This is expected, not a bug.

  `reply_count` drops every reply flagged as ignored, and that flag is set by three separate things:

  1. Out-of-office replies, but only when the *ignore out-of-office* toggle is on
  2. Replies whose sender is not the lead — forwards and self-sends
  3. Replies you ignore by hand in the master inbox

  `non_ooo_reply_count` only excludes out-of-office. Causes 2 and 3 are not out-of-office, so those replies are counted here while `reply_count` drops them.

  **Compare `non_ooo_reply_count` against `total_reply_count`, never against `reply_count`.**
</Warning>

<Note>
  If reply categorisation has not run for a campaign, leads stay uncategorised and `non_ooo_reply_count` equals `total_reply_count`.

  Categorisation is stored per lead and reflects that lead's most recently categorised reply. A lead that sends an out-of-office reply and later a genuine reply is counted as non-OOO for both.
</Note>

<RequestExample>
  ```bash cURL theme={null}
  curl "https://server.smartlead.ai/api/v1/campaigns/123/analytics-by-date?api_key=YOUR_KEY&start_date=2025-01-01&end_date=2025-01-31"
  ```

  ```python Python theme={null}
  import requests

  API_KEY = "YOUR_API_KEY"
  campaign_id = 123

  params = {
      "api_key": API_KEY,
      "start_date": "2025-01-01",
      "end_date": "2025-01-31",
      "time_zone": "America/New_York",
  }

  response = requests.get(
      f"https://server.smartlead.ai/api/v1/campaigns/{campaign_id}/analytics-by-date",
      params=params,
  )

  analytics = response.json()

  total = int(analytics["total_reply_count"])
  non_ooo = int(analytics["non_ooo_reply_count"])

  print(f"Total replies:  {total}")
  print(f"Non-OOO replies:{non_ooo}")
  print(f"Out-of-office:  {total - non_ooo}")
  ```

  ```javascript JavaScript theme={null}
  const API_KEY = 'YOUR_API_KEY';

  const params = new URLSearchParams({
    api_key: API_KEY,
    start_date: '2025-01-01',
    end_date: '2025-01-31',
    time_zone: 'America/New_York',
  });

  const response = await fetch(
    `https://server.smartlead.ai/api/v1/campaigns/123/analytics-by-date?${params}`
  );

  const analytics = await response.json();

  const total = Number(analytics.total_reply_count);
  const nonOoo = Number(analytics.non_ooo_reply_count);

  console.log(`Total replies:   ${total}`);
  console.log(`Non-OOO replies: ${nonOoo}`);
  console.log(`Out-of-office:   ${total - nonOoo}`);
  ```
</RequestExample>

## Response Example

<ResponseExample>
  ```json 200 theme={null}
  {
    "id": 123,
    "user_id": 456,
    "created_at": "2025-01-04T08:32:34.138Z",
    "status": "ACTIVE",
    "name": "Q1 Outbound",
    "start_date": "2025-01-01",
    "end_date": "2025-01-31",
    "sent_count": "23918",
    "unique_sent_count": "18262",
    "open_count": "7420",
    "unique_open_count": "5133",
    "click_count": "612",
    "unique_click_count": "489",
    "reply_count": "3929",
    "total_reply_count": "3929",
    "non_ooo_reply_count": "1502",
    "block_count": "0",
    "bounce_count": "1234",
    "unsubscribed_count": "18",
    "total_count": "122108",
    "drafted_count": "96904"
  }
  ```
</ResponseExample>

## Related Endpoints

* [Get Campaign Analytics](/api-reference/campaigns/get-analytics)
* [Get Top Level Analytics](/api-reference/campaigns/get-top-level-analytics)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.