---
title: "Retrieve a campaign"
method: GET
path: "/campaigns/{campaign_id}"
tags: ["campaigns"]
---

# Retrieve a campaign

`GET /campaigns/{campaign_id}`

## Request

- Base URL: `https://api.pscrpt.com/v1`
- URL: `https://api.pscrpt.com/v1/campaigns/{campaign_id}`
- Auth: one of:
  - HTTP bearer
  - OAuth 2
    - Authorization code: `https://api.pscrpt.com/oauth/authorize`
    - Token URL: `https://api.pscrpt.com/oauth/token` scopes: `campaigns:read`

## Headers

- `Postscript-Version` string, date

## Response `200`

The campaign. Config reads are read-your-writes; status/progress during a send is projected (seconds; `meta.as_of`).

- object
  - `data` Campaign, required: A one-shot send to an audience: mutable draft + immutable `cmv_` versions. PATCH/DELETE are legal only in `draft`/`scheduled` (else 409 `campaign_locked`). Per-campaign stats are served by Reports, never inlined here. `audience`, `messages`, `schedule`, and `tracking` are absent from reads until the campaign authoring surface serves them; the send nodes are `GET /v1/campaigns/{campaign_id}/messages`.
    - `archived_at` string, date-time
    - `audience` CampaignAudience: `lst_`/`seg_` IDs mixed freely (the prefix disambiguates). `include` = union; `exclude` subtracted from it. Evaluated exactly once, on entering `preparing`; membership changes after that instant have no effect on the send.
      - `exclude` string[]
      - `include` string[], required
    - `created_at` string, date-time, required
    - `current_version_id` string, nullable, required: Latest `cmv_` publication snapshot; null until first armed.
    - `id` string, required
    - `messages` CampaignMessageVariant[]
      - `body` string, nullable: Inline body — sms/mms only.
      - `channel` 'sms' | 'mms' | 'rcs' | 'email', required
      - `media_urls` string[], nullable: mms inline sends only.
      - `message_key` string, required: Stable client-supplied key, unique within the campaign, immutable once armed — the durable handle across drafts, versions, and per-variant reporting.
      - `split` number, nullable: Audience fraction — required on every variant when more than one; must total exactly 1.
      - `template_id` string, nullable: Draft ref — resolved to a pinned `tpv_` at arm.
      - `variables` object: Campaign-level values for the template's declared merge fields (`vars.*`); `profile.*` tags still resolve per recipient. Every `required` template variable must be covered by `send`/`schedule` time — else 400 `parameter_missing` naming the missing variables; keys not declared by the template → 400 `unknown_parameter`; unresolved optional tags follow the default chain (declared default → `default:` filter → empty string).
    - `name` string, required
    - `object` 'campaign', required
    - `schedule` CampaignSchedule
      - `mode` 'immediate' | 'scheduled', required
      - `send_at` string, date-time
      - `throttle` object, nullable
        - `per_minute` integer
      - `timezone_mode` 'property' | 'recipient': `property` sends at one property-local instant. `recipient` sends each recipient at the local time-of-day (and date) `send_at` denotes, in the recipient's timezone — the send spreads across the day. Null `location.timezone` falls back to the property timezone. Recipients whose local target instant has already passed when the campaign arms are sent immediately (time-sensitive promos must not slip a day). Past-`send_at` validation runs against the earliest timezone on Earth (UTC+14). `throttle.per_minute` applies to the whole campaign and may push some recipients past their local target time — documented, not a bug.
    - `send_completed_at` string, date-time, required
    - `send_started_at` string, date-time, required
    - `status` 'draft' | 'scheduled' | 'preparing' | 'active' | 'completed' | 'cancelled' | 'archived' | 'paused', required: `preparing`/`active` are system-driven; `completed`/`cancelled`/`archived` are terminal (`archived` via the archive-style DELETE). `paused` is a migrated campaign whose schedule is carried over but not armed; it never sends until armed.
    - `total_recipients` integer, nullable, required: Populated when `preparing` completes.
    - `tracking` CampaignTracking: UTM parameters applied to tracked links.
      - `utm_campaign` string
      - `utm_content` string
      - `utm_medium` string
      - `utm_source` string
      - `utm_term` string
    - `updated_at` string, date-time, required
  - `meta` Meta, required
    - `as_of` string, date-time
    - `livemode` boolean, required
    - `page` PageMeta
      - `limit` integer, required
      - `next_cursor` string, nullable, required: Pass as `page[after]`. Null on the last page.
    - `request_id` string, required
    - `resolved_from` string: The merged-away profile ID the request addressed; `data` holds the survivor.
    - `revision` string, date, required

## Other responses

- `400`: Malformed input, an unknown parameter, an unsupported filter, sort, or expand, or a mistyped ID.
- `401`: Missing or invalid API key.
- `403`: The key lacks the required scope.
- `404`: No such resource in the authenticated property.
- `429`: Rate limit exceeded.
- `500`: Internal error.
- `502`: Upstream provider failure.
- `503`: Temporarily unavailable.

## Changes

- **2026-10-02** `c0c9a9f0ac5c`: 1 info
  - endpoint added

[Operation history](https://skmtc.dev/pscrpt/apis/postscript-native-api-beta/changes/campaigns/:campaign_id/get.md)

---

[API](https://skmtc.dev/pscrpt/apis/postscript-native-api-beta.md) - [All operations](https://skmtc.dev/pscrpt/apis/postscript-native-api-beta/llms.txt) - [OpenAPI document](https://skmtc.dev/pscrpt/apis/postscript-native-api-beta/revisions/882458ee1f36?raw)
