# Reading campaigns

The three read endpoints: one campaign by id, the project's filtered campaign list, and a digest of the whole project's campaign activity. This page also covers the fields a campaign carries and how to collect the result of an asynchronous estimate or delete.

**Language:** en
**Audience:** developer
**TLDR:** GET /campaign/{id} returns the campaign under entity and is how you collect the result of an estimate (the counts, preparedUnits and the prices) and how you tell a finished delete from one still cleaning up. GET /campaign lists them under entities with Query Filter syntax, sorted by campaignAt desc, and hides direct sends. GET /campaign/summary is a project-wide digest: counts by phase and status, the last campaign sent with its figures, the next one scheduled, and the 30/90-day cadence, built from aggregates rather than a paged listing.
**Search keywords:** read campaign, get campaign, campaign status, check campaign, list campaigns, campaign list, campaign summary, digest, dashboard, overview, poll, polling, campaign fields, contactsCount, preparedUnits, priceUser, byPhase, PROJECT_CAMPAIGN_READ
**Related pages:** /developers/product-api/campaigns/overview, /developers/product-api/campaigns/estimating-and-deleting, /developers/further-reading/query-filter
**Docs index (every page):** https://docs.instasent.com/llms.txt
**This zone's index:** https://docs.instasent.com/developers/product-api/llms-full.txt
**This page:** https://docs.instasent.com/developers/product-api/campaigns/reading-campaigns/ (HTML) · https://docs.instasent.com/developers/product-api/campaigns/reading-campaigns.md (Markdown)

Reading is not only how you look a campaign up. It is the step that **closes the two asynchronous operations**: an estimate answers `202` and a delete answers `204`, and neither carries the outcome in its own response. Reading the campaign back is where you find it.

All three endpoints take the `PROJECT_CAMPAIGN_READ` scope and sit in the **large** rate-limit class.

## Reading one campaign

[`GET /v1/project/{project}/campaign/{id}` - View a campaign.](/developers/product-api/reference)

The campaign comes back under `entity`, in the same shape the creation endpoint returns.

```json
{
  "entity": {
    "id": "68b0f2a4e5a6b7c8d9e0f1a2",
    "title": "Summer sale announcement",
    "status": "estimated",
    "channelType": "sms",
    "purpose": "standard",
    "audienceContactsCount": 18400,
    "channelContactsCount": 17250,
    "contactsCount": 16980,
    "ignoredContactsCount": 270,
    "preparedUnits": 20376,
    "priceUser": { "value": 611.28, "currency": "EUR" },
    "campaignAt": "2026-06-15T09:30:00+02:00",
    "scheduledAt": null,
    "createdAt": "2026-06-01T11:04:22+00:00"
  }
}
```

The entity carries more than the example shows. The fields worth knowing by name group into four families:

| Family          | Fields                                                                                                                                                                                                                                                                                                  |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| What it is      | `id`, `title`, `emoji`, `description`, `favorite`, and the channel as a pair: `channelType` is the string (`sms` or `rcs`) and `channel` is the channel's own object, whose shape follows from it. `purpose` marks what kind of campaign this is; one you created through the API is always `standard`. |
| Where it is     | `status`, the raw status every operation in this section is gated by. See [Statuses and phases](/developers/product-api/campaigns/overview#statuses-and-phases).                                                                                                                                        |
| Who it reaches  | `audienceContactsCount`, `channelContactsCount`, `contactsCount` and `ignoredContactsCount`, explained below. The targeting itself reads back as `includeQuerySegments` and `includeQueryFilters`, with their `exclude` counterparts.                                                                   |
| What it costs   | `preparedUnits` is the billable message parts, with `unitsMin` and `unitsMax` bracketing it. `priceUser` is the cost applied to the account, with `priceMin` and `priceMax` around it. Every price is an object: `{ "value": 611.28, "currency": "EUR" }`.                                              |
| When it happens | `campaignAt` is the calendar anchor, `scheduledAt` the armed send time (`null` until a person confirms it), and `preparedAt`, `confirmedAt`, `canceledAt`, `createdAt` and `updatedAt` timestamp the rest. `localTime` and `localScheduledAt` give the same instants in the project's timezone.         |

**The four counts are easy to confuse**, and they answer different questions. `audienceContactsCount` is who the targeting resolves to, ignoring the channel. `channelContactsCount` narrows that to the contacts the channel can actually reach. `contactsCount` is what the estimate or the preparation actually resolved, and `ignoredContactsCount` is what it dropped on the way. A gap between the first two is a reachability problem; a gap between the last two is a consent or data problem.

Once a campaign has been sent it also carries `stats`, `channelBreakdown` and `statsAt`. Those are the summary figures; the full reporting surface is [campaign analytics](/developers/product-api/analytics/overview#campaign-report).

> **Note**: **A campaign in `estimating` reads as empty, and that is correct.** Starting an estimate clears the counts and the prices before the worker runs, so between the `202` and the worker landing you will read zeros and nulls. It is not a failed estimate. Wait for `status` to leave `estimating` before you believe the figures.

## Listing campaigns

[`GET /v1/project/{project}/campaign` - List the project's campaigns.](/developers/product-api/reference)

The list takes the shared [Query Filter](/developers/further-reading/query-filter) syntax for filtering, sorting and pagination, so `status_eq=sent` or `campaignAt_gte=2026-01-01T00:00:00+00:00` work here as they do on every other list endpoint. Campaigns come back under `entities`, with the window described under `metadata`:

```json
{
  "entities": [{ "id": "68b0f2a4e5a6b7c8d9e0f1a2", "title": "Summer sale announcement", "status": "sent" }],
  "metadata": { "count": 47, "start": 0, "limit": 50 }
}
```

Two behaviours are specific to this list:

- **It is sorted newest first by calendar date.** With no `_sort`, the default is `campaignAt:desc`.
- **`_limit` on its own is ignored.** Pagination needs both halves: send `_start=0&_limit=50`.

### What the list leaves out

The list only returns campaigns a marketer would recognise as campaigns. One-off direct sends are recorded as campaigns internally, carry `purpose: direct` rather than `standard`, and are **filtered out here unconditionally**. There is no parameter that brings them back, and a filter of your own on `purpose` cannot widen it.

This matters if you are reconciling volumes: the campaign list is not the record of everything the project sent. It is the record of everything the project *campaigned*.

## The project digest

[`GET /v1/project/{project}/campaign/summary` - A digest of the project's campaign activity.](/developers/product-api/reference)

One call that answers "what has this project been doing", without paging the list. It is built from aggregates over indexed queries and two point reads, so it stays cheap on projects with thousands of campaigns, and it is the natural thing to put behind a dashboard or hand to an assistant opening a conversation.

```json
{
  "entity": {
    "total": 47,
    "byPhase": { "draft": 3, "preparing": 1, "scheduled": 2, "sending": 0, "sent": 39, "stopped": 2 },
    "byStatus": { "draft": 3, "prepared": 1, "scheduled": 2, "sent": 39, "canceled": 2 },
    "lastSent": {
      "id": "68b0f2a4e5a6b7c8d9e0f1a2",
      "title": "Summer sale announcement",
      "channel": "sms",
      "sentAt": "2026-06-15T09:30:00+00:00",
      "sentAgo": "6 days ago",
      "recipients": 16980,
      "sent": 16980,
      "delivered": 16659,
      "deliveryRate": 0.9812,
      "clicks": 2143,
      "cost": 611.28,
      "statsAt": "2026-06-16T03:00:00+00:00"
    },
    "nextScheduled": {
      "id": "68b0f2a4e5a6b7c8d9e0f1b7",
      "title": "Restock notice",
      "channel": "rcs",
      "scheduledAt": "2026-06-22T10:00:00+00:00",
      "inHours": 18.5,
      "recipients": 4210
    },
    "cadence": { "sentLast30Days": 4, "sentLast90Days": 11 }
  }
}
```

| Field           | What it holds                                                                                                                                                |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `total`         | Every campaign in the project.                                                                                                                               |
| `byPhase`       | Counts in the six [phases](/developers/product-api/campaigns/overview#statuses-and-phases): `draft`, `preparing`, `scheduled`, `sending`, `sent`, `stopped`. |
| `byStatus`      | The same campaigns counted by raw status, for when the six buckets are too coarse.                                                                           |
| `lastSent`      | The most recent campaign that went out, with its headline figures. `null` if none ever did.                                                                  |
| `nextScheduled` | The next campaign due out, with `inHours` until it goes. `null` if nothing is armed.                                                                         |
| `cadence`       | How many campaigns went out in the last 30 and 90 days.                                                                                                      |

Two conveniences are worth noticing, because they save a round of arithmetic in a UI: `deliveryRate` arrives precomputed as a fraction between 0 and 1, and `sentAgo` arrives already in words ("6 days ago").

> **Tip**: **Do not rebuild this from the list.** A count by phase taken from page one of the campaign list is simply wrong from page two onwards, and paging a project's whole history to add up six numbers is expensive for an answer this endpoint already has.

## Collecting an asynchronous result

Both write operations on an existing campaign finish somewhere else. The pattern is the same for each: call, then re-read until the campaign tells you it is done.

#### 1. After an estimate

The `202` leaves the campaign in `estimating`. Re-read it until `status` changes, then take `contactsCount`, `ignoredContactsCount`, `preparedUnits` and the price fields. The worker typically takes about a minute, so poll on the order of seconds, not milliseconds.

#### 2. After a delete

The `204` has no body and does not always mean the campaign is gone. Re-read it: a **`404`** means it is gone, and the campaign still there in `deleted` means a worker is removing its messages and will finish on its own. Neither is an error, and an integration that treats a post-delete `404` as a failure will report problems that did not happen.

## What's next

- [Estimating and deleting](/developers/product-api/campaigns/estimating-and-deleting) - The two operations whose results you collect here, and the states each one accepts.
- [Query Filter](/developers/further-reading/query-filter) - The filtering, sorting and pagination syntax the campaign list shares with every other list endpoint.
- [Campaign analytics](/developers/product-api/analytics/overview) - The full reporting surface for a campaign that has been sent.

---

This is one page of the Instasent documentation. For the complete machine-readable index of every guide and API reference, fetch https://docs.instasent.com/llms.txt — start there for full context.
