Delivery status of campaigns, ad sets and ads
Every synced campaign, ad set and ad carries the platform's own status (Meta effective_status,
Google ENABLED/PAUSED/REMOVED, LinkedIn, TikTok). X3 normalizes it per level:
| Status | Label | Examples of platform values |
|---|---|---|
active | Aktiv | Meta ACTIVE, Google ENABLED, TikTok ENABLE |
paused | Pausiert | Meta/Google PAUSED |
paused_parent | Pausiert (Kampagne / Anzeigengruppe) | Meta CAMPAIGN_PAUSED, ADSET_PAUSED; an active ad in a paused campaign |
in_review | In Prüfung | Meta PENDING_REVIEW, IN_PROCESS, PREAPPROVED |
rejected | Abgelehnt / eingeschränkt | Meta DISAPPROVED, WITH_ISSUES; Google approval DISAPPROVED, limited |
learning / learning_limited | Lernphase / Lernphase eingeschränkt | Meta learning stage, Google primary status LEARNING |
ended | Beendet | budget used up, schedule over, LinkedIn COMPLETED |
draft | Entwurf | not started yet |
deleted | Gelöscht / archiviert | Meta ARCHIVED/DELETED, Google REMOVED |
unknown | Unbekannt | anything new |
Paused applies to children: the effective status of an ad in a paused campaign is
paused_parent with the level that paused it. Deleted parents make children deleted.
Where you see it
The ads report (GET /v1/projects/{project}/ads/report, MCP ads_performance) returns per row
status (own), effective_status, status_parent_level, status_since and status_raw (the platform's
value, shown in the tooltip). GET /v1/projects/{project}/ads/entities returns statusNorm,
effectiveStatus, statusParentLevel, statusSince and statusRaw (status stays the raw value).
GET /v1/projects/{project}/ads/status-history lists every change (newest first, filter by platform,
level, entity).
Filters
status (repeat or comma-separate effective statuses), quick and hide_deleted:
| quick | keeps |
|---|---|
active_or_spend | active entities plus every paused one with spend in the period (the dashboard default – „inkl. 6 pausierte mit Ausgaben“) |
active | active, learning and in review |
with_spend | everything with spend in the period |
all | everything |
hide_deleted defaults to on whenever a quick filter is given. Totals never change with the
filter: spend and leads of paused or deleted entities stay in the sums. The report answers
pausedWithSpend – the number of rows kept only because of their spend.
Freshness
Statuses update with every entity sync (every 6 hours) and with the lightweight status check
ads.status every hour (read-only status queries; never a change at the platform). Pausing, removing
or a rejection writes an annotation of kind ad_status into the time
series and re-checks the project's tasks.
Why a status is „Unbekannt“
Rows and entities carry status_reason (report) / statusReason (entities) when X3 cannot say more:
| Reason | Meaning |
|---|---|
no_permission | the platform refused the status read (403) – every entity of the account shows „Unbekannt“ until the account is reconnected with read access |
missing_field | the platform sends no status for this level |
not_listed | the last complete status listing no longer returned the entity – it counts as deleted, its history and numbers stay |
not_connected | the campaign is only known from spend or links of an ad account X3 is not connected to (it is labelled as such, never shown by id) |
check_failed | the last status check failed (rate limit, 5xx, timeout) – the last known status is shown |
The default quick filter active_or_spend keeps unknown rows: X3 cannot tell that they stopped.
The hourly status check
ads.status lists campaigns, ad sets and ads per account with status fields only (Meta
effective_status, ad set learning_stage_info and end_time; Google status, primary_status,
primary_status_reasons and the ad's policy_summary; LinkedIn campaign group, campaign and creative
status with the creative review; TikTok switch and secondary status), at most 25 list pages per
account and 60 per project and run. Entities missing from a listing are only marked deleted when the
listing reached its end within that budget. Each account keeps the outcome of its last check
(statusCheck in GET /v1/projects/{project}/ad-accounts: ok, rate_limited, error,
no_permission, expired, with retryAt); after a failure the account is checked again after 1 h,
2 h, 4 h and then every 6 h – never sooner than the platform's Retry-After. Statuses that only
changed because a parent changed (an ad of a paused campaign) write no own annotation; a campaign that
runs again writes „wieder aktiv“.
Background runs of ad accounts
Connecting an account starts one ads.backfill run per account, „Jetzt abgleichen“
(POST /v1/projects/{project}/ads/sync) one ads.sync_now run per account (runs in the answer;
see background runs). The run follows the account's sync windows
(„Abgleich läuft · 2 von 5 Zeiträumen“), is idempotent while it is active and ends with
connection_expired („Meta-Verbindung abgelaufen · neu verbinden“) when the platform refuses the
token. Account rows name who connected them (connectedBy, connectedVia, connectedAt); the
internal connection id is only part of the details.