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

# Campaign stats

> Progress counters plus `last_tick_result` — the engine's own account of what it
last did with this campaign, and the field to read when a `running` campaign looks
idle. Required scope `campaigns:read`.


Progress counters for one campaign, plus the diagnostic fields that answer "why is nothing happening right now". Counts are computed live at request time, not read from a cache, so they always agree with `GET /campaigns/{id}/leads`.

Required scope: `campaigns:read`.

## Path parameters

| Parameter | Type | Notes                                                |
| --------- | ---- | ---------------------------------------------------- |
| `id`      | uuid | Campaign ID. Must belong to the API key's workspace. |

There is no query parameter and no request body.

## Response fields

| Field                | Type            | Meaning                                                                                                           |
| -------------------- | --------------- | ----------------------------------------------------------------------------------------------------------------- |
| `campaign_id`        | uuid            | Echo of the path parameter.                                                                                       |
| `status`             | string          | Current campaign status.                                                                                          |
| `leads_by_status`    | object          | Contact counts keyed by contact status. Statuses with no contacts are omitted, so treat a missing key as `0`.     |
| `leads_total`        | integer         | Contacts enrolled.                                                                                                |
| `attempts_total`     | integer         | Dial attempts made across all contacts, including retries.                                                        |
| `attempts_in_flight` | integer         | Attempts not yet settled — calls ringing, talking, or waiting to be categorized. Compare against `max_in_flight`. |
| `calls_today`        | integer         | Calls started today, against `max_calls_per_day`. Resets at midnight in your workspace timezone.                  |
| `max_calls_per_day`  | integer         | The configured daily cap, for convenience.                                                                        |
| `spent_cents`        | integer         | Settled spend for this campaign.                                                                                  |
| `reserved_cents`     | integer         | Worst-case cost of the calls currently in flight.                                                                 |
| `estimate_cents`     | integer         | Pre-launch estimate, when one was computed.                                                                       |
| `budget_cents`       | integer \| null | The configured cap, or `null` for no cap. Enforced against `spent_cents + reserved_cents`.                        |
| `last_tick_at`       | timestamp       | When the campaign was last evaluated. While running this should be within the last couple of minutes.             |
| `last_tick_result`   | string          | Outcome of that evaluation. See the table below.                                                                  |
| `last_error`         | string \| null  | Last internal error message, truncated. Normally `null`.                                                          |
| `company_id`         | uuid            | Workspace this response belongs to.                                                                               |

## Contact statuses in `leads_by_status`

| Status                 | Meaning                                                                         |
| ---------------------- | ------------------------------------------------------------------------------- |
| `pending`              | Waiting for its turn.                                                           |
| `scheduled`            | Waiting for a retry timer to elapse.                                            |
| `dialing`              | A call is being placed or is in progress.                                       |
| `awaiting_disposition` | The call ended and is waiting to be categorized. Never re-dialed in this state. |
| `completed_success`    | A stop rule fired — this contact is done.                                       |
| `completed_failed`     | Finished without a successful outcome.                                          |
| `exhausted`            | Hit `max_attempts` without hitting a stop rule.                                 |
| `excluded`             | Removed by you, or retired by a stop / archive.                                 |
| `dnc`                  | On the do-not-call list. Never called again.                                    |

`pending`, `scheduled`, `dialing` and `awaiting_disposition` are the live states. When all four reach zero the campaign auto-completes.

## Reading `last_tick_result`

This is the field to check first when a `running` campaign appears idle. Most values are normal pacing, not faults.

| Value                               | Meaning                                                                                                                                     |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `admitted`                          | Calls were started on the last evaluation. Everything is working.                                                                           |
| `no_eligible_leads`                 | Nothing is due right now — every remaining contact is finished, in flight, or waiting on a retry timer.                                     |
| `spacing`                           | `min_seconds_between_calls` has not elapsed yet.                                                                                            |
| `daily_cap_reached`                 | `max_calls_per_day` hit for today.                                                                                                          |
| `max_in_flight`                     | This campaign already has its maximum number of calls outstanding.                                                                          |
| `outside_call_window`               | Outside your workspace calling hours.                                                                                                       |
| `no_reachable_call_window`          | Your calling hours leave no upcoming window at all. The campaign is paused as `paused_config`.                                              |
| `from_number_unavailable`           | The from-number is no longer active. Paused as `paused_config`.                                                                             |
| `agent_has_no_duration_cap`         | The agent's maximum call duration was removed, so worst-case spend is unbounded. Paused as `paused_config`.                                 |
| `insufficient_credit`               | Balance below the minimum needed to place a call. Paused as `paused_insufficient_credit`; auto-resumes on top-up.                           |
| `credit_reserve_would_breach_floor` | The balance covers a call today but not the worst case of the calls already in flight. Clears on its own as they settle, or after a top-up. |
| `budget_exhausted`                  | `budget_cents` reached, counting in-flight worst case. Paused as `paused_budget`.                                                           |
| `resumed_credit_ok`                 | Funds arrived and the campaign resumed itself.                                                                                              |
| `platform_admission_disabled`       | Campaign dialing is paused platform-wide. Nothing to fix on your side.                                                                      |
| `completed`                         | Every contact reached a terminal state.                                                                                                     |
| `error`                             | An internal error occurred; see `last_error`.                                                                                               |

If `last_tick_at` is more than a few minutes old on a `running` campaign, the campaign is not being evaluated at all — that is an infrastructure problem rather than a configuration one.

## Example request

```bash theme={null}
curl "https://api.goyappr.com/campaigns/CAMPAIGN_ID/stats" \
  -H "Authorization: Bearer $YAPPR_API_KEY"
```

## Example response

```json theme={null}
{
  "campaign_id": "b3f1c0d2-5a44-4f0e-9c11-7a2e8d3f0001",
  "status": "running",
  "leads_by_status": {
    "pending": 291,
    "scheduled": 44,
    "dialing": 1,
    "awaiting_disposition": 1,
    "completed_success": 58,
    "exhausted": 12,
    "excluded": 3,
    "dnc": 2
  },
  "leads_total": 412,
  "attempts_total": 194,
  "attempts_in_flight": 2,
  "calls_today": 37,
  "max_calls_per_day": 150,
  "spent_cents": 8140,
  "reserved_cents": 1000,
  "estimate_cents": 41200,
  "budget_cents": 50000,
  "last_tick_at": "2026-07-28T11:59:00.412Z",
  "last_tick_result": "spacing",
  "last_error": null,
  "company_id": "fe493f11-0000-0000-0000-000000000001"
}
```

`attempts_total` exceeds `completed_success + exhausted` because retries count as attempts: with `max_attempts: 3`, one contact can contribute up to three.

## Errors

| HTTP | Code                 | When                                                                 |
| ---- | -------------------- | -------------------------------------------------------------------- |
| 401  | `INSUFFICIENT_SCOPE` | API key lacks `campaigns:read`.                                      |
| 404  | —                    | No campaign with that ID in this workspace, or it has been archived. |
| 500  | —                    | The counters could not be computed. Safe to retry.                   |


## OpenAPI

````yaml GET /campaigns/{id}/stats
openapi: 3.1.0
info:
  title: Yappr API
  description: >
    Create and manage AI voice agents, purchase phone numbers, configure tools,
    and initiate calls — all via REST.
  version: 1.0.0
  contact:
    url: https://goyappr.com
servers:
  - url: https://api.goyappr.com
    description: Production
security:
  - apiKey: []
paths:
  /campaigns/{id}/stats:
    parameters:
      - in: path
        name: id
        required: true
        schema:
          type: string
          format: uuid
    get:
      tags:
        - Campaigns
      summary: Get campaign stats
      description: >
        Progress counters plus `last_tick_result` — the engine's own account of
        what it

        last did with this campaign, and the field to read when a `running`
        campaign looks

        idle. Required scope `campaigns:read`.
      operationId: getCampaignStats
      responses:
        '200':
          description: Campaign stats
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CampaignStats'
        '404':
          description: Not found
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  schemas:
    CampaignStats:
      type: object
      description: >
        Live progress for one campaign, plus the engine's own account of what it
        last did. When a `running` campaign appears to be doing nothing,
        `last_tick_result` and `last_error` are the fields that explain it.
      properties:
        campaign_id:
          type: string
          format: uuid
        status:
          type: string
          description: Same value as `Campaign.status`.
        leads_by_status:
          type: object
          additionalProperties:
            type: integer
          description: >-
            Contact counts keyed by `CampaignContact.status`. Statuses with no
            contacts are omitted.
          example:
            pending: 812
            awaiting_disposition: 2
            completed_success: 141
            exhausted: 45
        leads_total:
          type: integer
        attempts_total:
          type: integer
          description: >-
            Every dial attempt this campaign has made, including platform-side
            retries.
        attempts_in_flight:
          type: integer
          description: >-
            Attempts not yet settled — dialing, live, or awaiting
            classification.
        calls_today:
          type: integer
          description: Calls handed to the queue in the current day window.
        max_calls_per_day:
          type: integer
        spent_cents:
          type: integer
        reserved_cents:
          type: integer
        estimate_cents:
          type: integer
          nullable: true
        budget_cents:
          type: integer
          nullable: true
        last_tick_at:
          type: string
          format: date-time
          nullable: true
        last_tick_result:
          type: string
          nullable: true
          description: >-
            Machine-readable reason the last evaluation admitted, or did not
            admit, a call.
        last_error:
          type: string
          nullable: true
        company_id:
          type: string
          format: uuid
    Error:
      type: object
      properties:
        error:
          type: string
        code:
          type: string
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      bearerFormat: API Key
      description: >-
        Your Yappr API key (e.g. `ypr_live_...`). Generate one in the dashboard
        under **Settings → API Keys**.

````