# Campaign Post Checker · Brief Compliance & Post Metrics (`doesaiknow/campaign-post-checker`) Actor

Paste influencer post URLs from TikTok, Instagram, YouTube, Facebook and X plus your brief rules. Get one row per post: live or unavailable, publish date, author, views, likes, comments, #ad and paid-partnership signals, and pass, fail or unknown per rule with what is missing.

- **URL**: https://apify.com/doesaiknow/campaign-post-checker.md
- **Developed by:** [David S](https://apify.com/doesaiknow) (community)
- **Categories:** Social media, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.04 / post check

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Campaign Post Checker: brief compliance and post metrics for influencer campaigns

Campaign Post Checker takes a list of influencer post URLs from TikTok, Instagram, YouTube, Facebook and X, plus the rules from your campaign brief. For every post it returns one row: is the post still live, when it was published, who published it, views, likes, comments, shares, the platform's paid-partnership label, and a pass, fail or unknown result for each brief rule with a short list of what is missing.

This is an **operational check, not evidence for a dispute and not legal advice**. It tells your team which deliverables need a follow-up today. Disclosure rules differ between the FTC, the ASA, the UOKiK and other regulators, so every rule is configurable and the defaults are only a starting point.

### What Campaign Post Checker does

- **Checks that the post is live.** Each row has `status`: `live`, `unavailable`, `unsupported` or `error`. A post the platform does not show publicly is `unavailable` with `unavailableReason: "not_found"`; on TikTok, Instagram, YouTube and X this covers deleted posts and also private or login-only ones, because the platform answers the same way. On Facebook a deleted post cannot be told apart from one behind a login, so the reason is `unknown`.
- **Reads the post the way a viewer sees it.** Caption or description, publish time in UTC, author handle and name, links, hashtags, @mentions, video length and content type.
- **Collects public metrics.** Views, likes, comments, shares and saves as whole numbers. A metric the platform does not show is `null`, never `0`, and `metricsAvailability` says which ones were provided.
- **Checks your brief.** Required phrases and hashtags, @mentions, promo codes, links to your domain, disclosure text and its position, the platform's paid-partnership label, a publication window in your time zone, minimum video length and forbidden phrases.
- **Explains every result.** Each rule produces a check with what was expected, what was found, where it was found and why it failed or could not be confirmed.

### Supported platforms and what is not covered

| Platform | Post types | Paid-partnership label | Metrics |
|---|---|---|---|
| TikTok | videos, photo posts | yes (`is_paid_partnership`), plus ad and commission labels | views, likes, comments, shares, saves |
| Instagram | posts, carousels, reels | yes | views (video), likes, comments |
| YouTube | videos, Shorts, live replays | "Includes paid promotion" label | views, likes, comments |
| Facebook | posts, videos, reels | not provided | views (video), reactions, comments, shares |
| X (Twitter) | posts | not provided | views, likes, replies, reposts, bookmarks |

Not covered in this version: Stories on any platform, LinkedIn, Threads, links in the profile bio, pinned comments, spoken mentions inside the video, and screenshots of the post. Story, LinkedIn and Threads URLs come back as `unsupported` and cost nothing. Private or login-only posts cannot be read: they come back as `unavailable` with reason `not_found` (charged as a check on TikTok, Instagram, YouTube and X) or `unknown` (Facebook, free).

### How to check influencer campaign posts

1. Open the Actor and paste your post URLs into **Posts to check**. Add `campaignId`, `deliverableId` and `expectedAuthor` if you want them echoed back in each row.
2. Put the rules shared by the whole campaign into **Brief rules**. Override any rule for a single post with that item's own `rules`.
3. Start the run. Results appear in the **Overview** table in input order; the **Issues to follow up** view lists what needs attention.
4. Export results to CSV, Excel or JSON.
5. Save the input as a task and schedule it for the campaign window.

### Ready-made tasks

- [Influencer campaign tracking: post status, metrics and #ad](https://apify.com/doesaiknow/campaign-post-checker/examples/influencer-campaign-tracking)
- [Instagram post insights by URL](https://apify.com/doesaiknow/campaign-post-checker/examples/instagram-post-insights-by-url)
- [TikTok video analytics by URL](https://apify.com/doesaiknow/campaign-post-checker/examples/tiktok-video-analytics-by-url)
- [YouTube video stats checker with paid promotion label](https://apify.com/doesaiknow/campaign-post-checker/examples/youtube-video-stats-checker)
- [Instagram reel views checker](https://apify.com/doesaiknow/campaign-post-checker/examples/instagram-reel-views-checker)
- [Tweet analytics by URL](https://apify.com/doesaiknow/campaign-post-checker/examples/tweet-analytics-by-url)
- [YouTube sponsorship checker](https://apify.com/doesaiknow/campaign-post-checker/examples/youtube-sponsorship-checker)

### Brief rules example

```json
{
  "items": [
    { "url": "https://www.tiktok.com/@creator_handle/video/7400000000000000000", "deliverableId": "TT-01", "expectedAuthor": "@creator_handle" },
    { "url": "https://www.instagram.com/reel/AbCdEfGhIjK/", "deliverableId": "IG-01", "rules": { "minDurationSec": 30 } }
  ],
  "defaultRules": {
    "requiredHashtags": ["#brandname"],
    "requiredMentions": ["@brandname"],
    "promoCode": "GLOW20",
    "mustLinkDomain": ["brand.com"],
    "disclosureText": ["#ad", "paid partnership"],
    "disclosurePosition": "firstNChars",
    "firstN": 125,
    "disclosureFlag": true,
    "disclosureMode": "OR",
    "publishedBetween": { "from": "2026-10-01", "to": "2026-10-15", "tz": "Europe/London" },
    "forbiddenPhrases": ["guaranteed results"]
  }
}
```

Matching is case-insensitive and respects word boundaries by default, so `#ad` does not match `#adventure` and the code `GLOW20` does not match `GLOW2025`. Text is normalized first (Unicode NFKC, zero-width characters removed, line breaks treated as spaces), so stylized letters and line breaks inside "paid partnership" still match.

### Output example

Illustrative row (shortened):

```json
{
  "deliverableId": "TT-01",
  "platform": "tiktok",
  "url": "https://www.tiktok.com/@creator_handle/video/7400000000000000000",
  "status": "live",
  "postedAt": "2026-10-13T11:46:48.000Z",
  "author": { "handle": "creator_handle", "name": "Creator", "verified": null },
  "authorMatches": true,
  "hashtags": ["#ad", "#brandname", "#skincare"],
  "metrics": { "views": 7724, "likes": 1052, "comments": 12, "shares": 21, "saves": 166 },
  "flags": { "paidPartnership": true, "isAd": false, "eligibleForCommission": false },
  "compliance": {
    "result": "fail",
    "missing": ["promo code GLOW20"],
    "checks": [
      { "rule": "disclosure", "result": "pass", "severity": "critical", "expected": "disclosure text OR platform label", "found": "pass/pass", "where": "text, flags", "reason": null },
      { "rule": "promoCode", "result": "fail", "severity": "major", "expected": "GLOW20", "found": null, "where": "text", "reason": "Promo code not found as a whole token (presence only; the code itself is not validated)." }
    ]
  },
  "evidence": { "fetchedAt": "2026-10-15T10:00:02.000Z", "adapterVersion": "tiktok@1.0.0", "rulesVersion": "r-3f9c0d2a7b1e4c55", "contentHash": "sha256:…" }
}
```

`evidence` records when the post was read, the parser and rules versions, and a hash of the post content; a changed `contentHash` between scheduled runs means the caption, links or labels changed.

### How pass, fail and unknown are decided

- **fail**: at least one rule was checked and is not met, for example a missing hashtag or a forbidden phrase.
- **unknown**: nothing failed, but at least one rule could not be confirmed from the data. Typical cases: the brief requires a link and the post has no links (it may be in the bio, which is not checked), the post links through a shortener, or the brief requires a paid-partnership label on a platform that does not expose it.
- **pass**: every configured rule was confirmed.

A missing observation is never counted as a pass. Disclosure is reported twice: as text in the caption and as the platform label. `disclosureMode: "OR"` accepts either; `"AND"` requires both.

### Monitoring schedule for the campaign window

Add an Apify schedule to your task. A common cadence is day 0, 7, 14, 30, 60 and 90 after publication. Every run is a fresh dated snapshot: compare metrics and spot posts that went from `live` to `unavailable` or whose caption changed.

### Pricing

Pay per event: one **post-check** event per post that returns a result, that is a `live` post or an `unavailable` post the platform reports as not found. Reference price: $0.05 per check on the free Apify plan and $0.04 on paid plans; see the Pricing tab for your plan. Rows with `error`, `unsupported` or `unavailable: unknown` are free; duplicates are checked once.

Control your spend with the run's maximum cost. When it is reached, the remaining posts get `statusReason: "run_budget_exhausted"` and are not checked or charged.

On the free Apify plan, each run checks up to 10 posts; the rest get `statusReason: "free_plan_run_limit"` and cost nothing.

### Integrations and API

Use the Apify API or send results to Google Sheets, Slack, n8n, Make and Zapier. AI agents can call the Actor through the Apify MCP server at mcp.apify.com.

### What this actor does not do

- It does not give legal advice or decide whether a post complies with advertising law.
- It does not prove that a post stayed online between two checks; it records what was visible at the time of each check.
- It does not check spoken words in the video, images, or whether a promo code works at checkout.
- It does not follow shortened links to their destination.

### FAQ

#### How do I check if an influencer post is still live?

Add the post URL to the input and run the Actor. A post that the platform reports as gone comes back as `unavailable` with reason `not_found`; a live post comes back as `live` with fresh metrics.

#### Can I check #ad and paid partnership disclosure?

Yes. The default rules look for #ad, #sponsored, #paidpartnership, "paid partnership", #gifted and "advertisement" in the caption, and you can require the platform's paid-partnership label as well. Both signals are reported separately.

#### Why is a result unknown instead of pass?

The Actor only passes what it can see in the post data. A link that may sit in the bio, a shortened link or a label that the platform does not expose leaves the rule unconfirmed, and the check says why.

#### Is the output enough to withhold payment from a creator?

Treat it as a dated operational record of what to follow up. For disputes, keep your own screenshots and contracts.

### Legal & support

The Actor reads public post information: captions, public metrics, public profile handles and platform labels. It does not log in and does not read private messages or data behind a login. The caption is returned as published, so it can contain contact details the author chose to post, such as a business email. You decide what to keep and for how long, and you are responsible for using the results in line with platform terms, privacy law and your agreements with creators. Questions and bug reports: use the Issues tab on this page.

# Actor input Schema

## `items` (type: `array`):

Up to 500 post URLs per run. Each item is either a URL string or an object: {"url": "…", "campaignId": "…", "deliverableId": "…", "expectedAuthor": "@handle", "rules": {…}}. Supported: TikTok videos and photo posts, Instagram posts and reels, YouTube videos and Shorts, Facebook posts, videos and reels, X posts. Stories, LinkedIn and Threads are returned as unsupported and are not charged. The same post listed twice is checked once.

## `defaultRules` (type: `object`):

Rules checked on every post unless an item sets the same rule in its own "rules" (an item value replaces the default for that rule; lists are not merged). Available: mustContainAny, mustContainAll, forbiddenPhrases, requiredHashtags, requiredMentions, promoCode, mustLinkDomain, linkScope ("caption" or "descriptionLinks"), disclosureText, disclosurePosition ("anywhere" or "firstNChars"), firstN, disclosureFlag, disclosureMode ("OR" or "AND"), publishedBetween {from, to, tz}, minDurationSec, caseSensitive, tokenBoundaries. Leave empty to use the default disclosure phrases only (#ad, #sponsored, #paidpartnership, paid partnership, #gifted, advertisement).

## `platformsAllowed` (type: `array`):

Posts from platforms not selected here are returned as unsupported ("platform_not_allowed") and are not fetched or charged. Default: all five.

## Actor input object example

```json
{
  "items": [
    {
      "url": "https://www.tiktok.com/@hetawoodard/video/7696117526312307990",
      "campaignId": "demo-campaign",
      "deliverableId": "tiktok-01",
      "expectedAuthor": "@hetawoodard",
      "rules": {
        "requiredHashtags": [
          "#skincare"
        ]
      }
    },
    {
      "url": "https://www.facebook.com/reel/2166091230582141",
      "campaignId": "demo-campaign",
      "deliverableId": "facebook-01"
    },
    {
      "url": "https://x.com/Nike/status/2109382322516799677",
      "campaignId": "demo-campaign",
      "deliverableId": "x-01"
    }
  ],
  "defaultRules": {
    "disclosureText": [
      "#ad",
      "#sponsored",
      "#paidpartnership",
      "paid partnership",
      "#gifted",
      "advertisement"
    ],
    "disclosureFlag": true,
    "disclosureMode": "OR"
  },
  "platformsAllowed": [
    "tiktok",
    "instagram",
    "youtube",
    "facebook",
    "x"
  ]
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "items": [
        {
            "url": "https://www.tiktok.com/@hetawoodard/video/7696117526312307990",
            "campaignId": "demo-campaign",
            "deliverableId": "tiktok-01",
            "expectedAuthor": "@hetawoodard",
            "rules": {
                "requiredHashtags": [
                    "#skincare"
                ]
            }
        },
        {
            "url": "https://www.facebook.com/reel/2166091230582141",
            "campaignId": "demo-campaign",
            "deliverableId": "facebook-01"
        },
        {
            "url": "https://x.com/Nike/status/2109382322516799677",
            "campaignId": "demo-campaign",
            "deliverableId": "x-01"
        }
    ],
    "defaultRules": {
        "disclosureText": [
            "#ad",
            "#sponsored",
            "#paidpartnership",
            "paid partnership",
            "#gifted",
            "advertisement"
        ],
        "disclosureFlag": true,
        "disclosureMode": "OR"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("doesaiknow/campaign-post-checker").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "items": [
        {
            "url": "https://www.tiktok.com/@hetawoodard/video/7696117526312307990",
            "campaignId": "demo-campaign",
            "deliverableId": "tiktok-01",
            "expectedAuthor": "@hetawoodard",
            "rules": { "requiredHashtags": ["#skincare"] },
        },
        {
            "url": "https://www.facebook.com/reel/2166091230582141",
            "campaignId": "demo-campaign",
            "deliverableId": "facebook-01",
        },
        {
            "url": "https://x.com/Nike/status/2109382322516799677",
            "campaignId": "demo-campaign",
            "deliverableId": "x-01",
        },
    ],
    "defaultRules": {
        "disclosureText": [
            "#ad",
            "#sponsored",
            "#paidpartnership",
            "paid partnership",
            "#gifted",
            "advertisement",
        ],
        "disclosureFlag": True,
        "disclosureMode": "OR",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("doesaiknow/campaign-post-checker").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "items": [
    {
      "url": "https://www.tiktok.com/@hetawoodard/video/7696117526312307990",
      "campaignId": "demo-campaign",
      "deliverableId": "tiktok-01",
      "expectedAuthor": "@hetawoodard",
      "rules": {
        "requiredHashtags": [
          "#skincare"
        ]
      }
    },
    {
      "url": "https://www.facebook.com/reel/2166091230582141",
      "campaignId": "demo-campaign",
      "deliverableId": "facebook-01"
    },
    {
      "url": "https://x.com/Nike/status/2109382322516799677",
      "campaignId": "demo-campaign",
      "deliverableId": "x-01"
    }
  ],
  "defaultRules": {
    "disclosureText": [
      "#ad",
      "#sponsored",
      "#paidpartnership",
      "paid partnership",
      "#gifted",
      "advertisement"
    ],
    "disclosureFlag": true,
    "disclosureMode": "OR"
  }
}' |
apify call doesaiknow/campaign-post-checker --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,doesaiknow/campaign-post-checker"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/jqSqFh3J99AhqlEHV/builds/fJ7m3jYUFH7g530WJ/openapi.json
