---
title: "Get Project Dashboard"
method: GET
path: "/api/app-auto-gtm/projects/{project_id}/dashboard"
tags: ["AppAutoGtm"]
---

# Get Project Dashboard

`GET /api/app-auto-gtm/projects/{project_id}/dashboard`

Return KPI aggregate for all campaigns in a project.

Query params:
    period: 'today' | '7d' | '30d' | 'all' (default '7d')
    campaign: optional segment_id to restrict to one campaign

Ownership: project.org_id must match the caller's current org.
Daily breakdown, last-replies and total_hot_leads_today are scoped to
the selected campaign when `campaign` is set, otherwise the whole
project. The campaign_rollup list is always empty for a single-campaign
view (one row would be redundant).

## Path parameters

- `project_id` integer, required

## Query parameters

- `period` 'today' | '7d' | '30d' | 'all'
- `campaign` integer, nullable

## Cookies

- `ex_access` string, nullable
- `ex_org` string, nullable

## Response `200`

Successful Response

- ProjectDashboardResponse
  - `project_id` integer, required
  - `period` 'today' | '7d' | '30d' | 'all', required
  - `total_emails_sent` integer, required
  - `total_replies` integer, required
  - `total_auto_replies` integer, required
  - `overall_reply_rate_pct` number, required
  - `total_hot_leads` integer, required
  - `total_hot_leads_today` integer, required
  - `total_spend_usd` number, required
  - `campaign_rollup` CampaignRollupItem[], required
    - `segment_id` integer, required
    - `campaign_name` string, required
    - `status` string, required
    - `emails_sent` integer, required
    - `total_replies` integer, required
    - `reply_rate_pct` number, required
    - `hot_leads` integer, required
    - `spend_usd` number, required
    - `cost_per_lead_usd` number, required
    - `daily_budget_usd` number, required
    - `subtext` string, required
    - `pool_used` integer, required
    - `pool_total` integer, required
    - `pool_pending` integer
    - `status_reason` string, nullable
    - `status_display` StatusDisplay — Unified status display object added to every client-facing segment payload. Computed once in _build_status_display; emitted as `status_display` on the wire. All legacy fields (status, paused, status_reason, agent_turn_running) remain unchanged for deploy-skew safety. Iteration-1 value vocabulary: not_started | searching | review | outreach | paused | budget_paused | listening | resuming | archived | error (`budget_paused` is the display-only $0-budget mask — see _ZERO_BUDGET_MASKED_STATES; no matching lifecycle status exists.)
      - `value` string, required
      - `label` string, required
      - `tone` string, required
      - `animated` boolean, required
      - `hint` string, required
      - `detail` string, required
      - `manual_start_blocked` boolean
    - `manual_status_counts` object
    - `cold_lost` integer
    - `emails_sent_7d` integer
    - `created_by` string, nullable
  - `lead_status_vocab` LeadStatusVocabItem[], required
    - `name` string, required
    - `color` string, required
    - `order` integer, required
  - `daily_breakdown` DailyBreakdownItem[], required
    - `day` string, date, required
    - `emails_sent` integer, required
    - `spend_usd` number, required
    - `replies` integer
    - `reply_rate_pct` number
    - `hot_leads` integer
    - `engaged_count` integer
    - `engaged_pct` integer
    - `cost_per_lead_usd` number
  - `performance_series` PerformancePoint[], required
    - `bucket_start` string, date, required
    - `bucket_end` string, date, required
    - `label` string, required
    - `emails_sent` integer, required
    - `reply_rate_pct` number, required
    - `hot_leads` integer, required
    - `spend_usd` number, required
  - `last_replies` LastReplyItem[], required
    - `person_id` string, required
    - `segment_id` integer, required
    - `company_name` string, nullable, required
    - `received_at` string, required
    - `intent` string, nullable, required
    - `snippet` string, nullable, required
    - `person_name` string, nullable
    - `campaign_name` string
    - `reply_status` 'not_replied' | 'replied' | 'agent_replied' | 'auto_handled'
    - `avatar_url` string, nullable
    - `need_reply` boolean
  - `cta` union
    - CtaSetupStep
      - `type` 'setup_step'
      - `payload` CtaSetupStepPayload, required
        - `step` 'csv_import' | 'hubspot' | 'api_key', required
    - CtaAutopilot
      - `type` 'autopilot'
    - CtaInbox
      - `type` 'inbox'
      - `payload` CtaInboxPayload, required
        - `hot_count` integer, required
        - `people` CtaPerson[], required
          - `person_id` string, required
          - `person_name` string, required
          - `avatar_url` string, nullable
          - `received_at` string, required
    - CtaBilling
      - `type` 'billing'
      - `payload` CtaBillingPayload, required
        - `running_campaigns_count` integer, required
    - CtaAutoReply
      - `type` 'auto_reply'
    - CtaReferral
      - `type` 'referral'
    - CtaZeroBudget
      - `type` 'zero_budget'
      - `payload` CtaZeroBudgetPayload, required
        - `running_campaigns_count` integer, required
        - `suggested_budget_usd` integer, required
    - CtaNoCard
      - `type` 'no_card'
      - `payload` CtaNoCardPayload, required
        - `running_campaigns_count` integer, required
        - `prepaid_covered` boolean
  - `day_off_until` string, date, nullable
  - `status_line` StatusLineResponse, required — Wire shape for the cabinet status line — T-2712 trimmed it to what the v5 plaque actually draws. ``plaque`` is the state; the three counters below fill the healthy family's detail line. ``plaque = None`` means "show no plaque", which is a real state, not a gap: the money CTAs carry their own verdict.
    - `plaque` StatusPlaque — The winning plaque state + everything its buttons need. One flat model rather than a per-state discriminated union: the states differ by two or three fields, not by shape, and a union of eleven payloads would be more wire vocabulary than logic. What each field means is always the same — which campaigns the buttons act on, how many campaigns the state covers, and the one campaign the copy names.
      - `state` 'start_campaign' | 'retry_launch' | 'resume' | 'review' | 'all_archived' | 'leads_exhausted' | 'setting_up' | 'sending' | 'scheduled' | 'budget_tomorrow' | 'next_batch', required
      - `segments` PlaqueSegment[]
        - `segment_id` integer, required
        - `name` string
        - `daily_limit_usd` number
      - `campaign_name` string, nullable
      - `campaigns_count` integer
      - `icp_exhausted` boolean
    - `next_send_at` string, nullable
    - `sent_today` integer, required
    - `queued` integer, required
  - `balance` BalanceTile — The org wallet, shown on the dashboard's budget tile (T-2710). The verdicts are the SERVER's: ``is_low`` / ``is_empty`` are read off the same billing constants the Billing page uses, never re-derived on the client — two thresholds for one wallet is the drift the T-642 invariant bans. ``is_empty`` (below one email's cost) implies ``is_low``; an org in debt has a negative balance, so both read true.
    - `balance_usd` number, required
    - `balance_credits` number, required
    - `is_empty` boolean, required
    - `is_low` boolean, required
  - `setup_steps` SetupStepResponse[], required
    - `key` string, required
    - `done` boolean, required
    - `data_backed` boolean, required

## Other responses

- `422` — Validation Error

---

[API](https://skmtc.dev/explee/apis/explee-api.md) · [All operations](https://skmtc.dev/explee/apis/explee-api/llms.txt) · [OpenAPI document](https://skmtc-service-production.skmtc.workers.dev/v1/apis/explee/explee-api/revisions/f169bc6e9447/schema)
