# Brand Deal Check - Who Is Actually Getting Paid? (`reapx/brand-collab-detector`) Actor

See who is actually getting paid, and by who. Every paid partnership tag, branded content label and sponsor mention on a creator's posts - the deals they announced and the ones they did not.

- **URL**: https://apify.com/reapx/brand-collab-detector.md
- **Developed by:** [Tarek Etman](https://apify.com/reapx) (community)
- **Categories:** For creators, Social media, Marketing
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $7.00 / 1,000 brand collaboration item collecteds

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

![reapX - the record of what changed](https://reapx.dev/reapx.gif)

## Brand Deal Check - Who Is Actually Getting Paid?

> *"They swore this was their first deal in the category. Was it?"*

You are about to sign a creator, or renew one, or explain to a client why last quarter's spend
went nowhere. The media kit says one thing. Their last thirty posts say another, and nobody has
time to open thirty posts and read every caption.

So you get the answer instead. Drop a handle in and you get back, in one sentence per post, who
they name, whether they marked it as an ad, and the number nobody hands you: **what a brand
post on that account is actually worth compared to a post they made for themselves.**

### The sentence you get back

This is a real row from a real run, not an illustration:

> **@gymshark names @Belcast in this caption and there is no ad marker on it. It has 847K views,
> and their brand posts land within a few percent of their own, so the audience treats them the
> same. For every 100 people following @gymshark, a post naming a brand reaches about 1.7 of
> them - that is the number to divide their rate by, and the one that compares to anyone else
> you are looking at. Across the 13 posts read here 5 name a brand and 5 of those carry no
> marker.**

Read it once and you have the negotiation. Paste it into a deck and every number in it is a
field on the same row, so it survives being questioned.

And when a creator is clean, it says so in the same voice, because "this one tags their deals
and here is how I know" is half the reason you asked:

> **@creator's caption reads as their own, with no brand named and no ad marker. Across the 13
> posts read here every one of the posts naming a brand is marked, so this is a creator who
> tags their deals.**

***

### The three numbers you came here not knowing you could get

Anyone can list which posts mention a brand. These are the ones that decide money.

**1. What their brand posts do against their own posts.** `brandPostViewGapPct` is the median
of every post where they name a brand, against the median of the posts where they do not. A
creator whose sponsored work runs 40% below their organic work is selling you a follower count
they cannot deliver against, and it never shows up in a media kit. When it runs *above*, that is
a creator whose audience does not tune out the ads, and that is worth paying more for.

**2. The one figure that carries to the next creator on your list.**
`brandPostReachPerFollowerPct` - for every 100 people following them, how many actually see a
post that names a brand. Everything else about a creator is measured against their own baseline
and dies there. This one divides straight into a rate card, so you can put four creators with
wildly different follower counts on one line and see which one is expensive.

**3. How often they mark a deal at all.** `disclosureRatePct` across everything read, plus
`brandIsRecurring` and `brandPostCount` so you can tell a one-off gift from a standing
arrangement with your competitor. That is the category-exclusivity question, answered before the
contract rather than after.

Plus the thing that only shows up if someone is looking: a post that named a brand and **is not
there any more**. `postAvailable` goes false and the row still tells you which brand it named.

***

### ⬇️ What you give us

Two fields, and the second one has a sensible default.

| Field | What to put in it |
|---|---|
| `handlesOrPosts` | A creator, and we go through everything they have posted lately - a TikTok `@handle`, or an Instagram one as `instagram.com/handle` or `ig:handle`. Or paste links to the exact posts you want checked; Instagram posts and reels and TikTok videos all work. Mix handles and links however you like. |
| `maxPosts` | A ceiling on the run so a long list cannot surprise you. Defaults to 20. |

```json
{
  "handlesOrPosts": ["@gymshark", "@lorengray"],
  "maxPosts": 20
}
```

Captions are read exactly as the creator wrote them, hashtags and mentions intact.

***

### ⬆️ What comes back

One row per post. The sentence is first; everything under it is the proof.

| Field | Type | What it is |
|---|---|---|
| `whatIFound` | String | The answer, written to be read and pasted. Every number in it is a field below. |
| `postId` | String | Stable per-post key, so you can diff two runs weeks apart. |
| `username` | String | The account the post belongs to. |
| `followerCount` | Integer | Followers on that account when we read it. |
| `platform` | String | `tiktok` or `instagram`. |
| `postUrl` | String | The post itself. One click to check us. |
| `postAvailable` | Boolean | False when the post is no longer up. |
| `caption` | String | The caption as written. |
| `brandName` | String | The brand this post names, when it names one. |
| `brandsMentioned` | Array | Everyone else named in the same caption. |
| `disclosed` | Boolean | Whether the post carries an ad marker. `null` when there is nothing to mark. |
| `disclosureTokens` | Array | The exact marker found, in the creator's own words. |
| `collabType` | String | `paid_partnership_tag`, `brand_mention_only` or `product_seeding`. |
| `views` | Integer | Views on this post. |
| `likeCount` | Integer | Likes on this post. |
| `viewsVsFollowersPct` | Number | This post's views as a share of their followers. |
| `brandPostViewsMedian` | Integer | Median views across their posts that name a brand. |
| `ownPostViewsMedian` | Integer | Median views across their posts that do not. |
| `brandPostViewGapPct` | Number | The gap between those two. Negative means brand posts underperform. |
| `brandPostReachPerFollowerPct` | Number | Reach per 100 followers on brand posts. The comparable one. |
| `ownPostReachPerFollowerPct` | Number | The same figure on their own posts. |
| `postsReadForCreator` | Integer | How many of their posts this run read. |
| `brandPostsForCreator` | Integer | How many of those name a brand. |
| `disclosedBrandPosts` | Integer | How many of those carry a marker. |
| `disclosureRatePct` | Number | The share that do. |
| `brandPostCount` | Integer | How many posts in this run name this same brand. |
| `brandIsRecurring` | Boolean | True when the same brand appears more than once. |
| `viewGapUnavailableReason` | String | Named when the gap could not be computed, so a blank is never a mystery. |
| `checkStatus` | String | `checked`, or the reason we could not read something. |
| `scrapedAt` | String | When we read it. |

#### A real row

From run `ycl3mTTfCYRFHJNyr`, unedited:

```json
{
  "checkStatus": "checked",
  "postId": "tt_7660964717631999254",
  "platform": "tiktok",
  "postUrl": "https://www.tiktok.com/@gymshark/video/7660964717631999254",
  "username": "gymshark",
  "followerCount": 6600000,
  "postAvailable": true,
  "brandName": "Belcast",
  "brandsMentioned": ["Belcast", "Annabel Lucinda", "Patty", "James Beardwell", "SWOLE", "sawyer_klatt"],
  "disclosed": false,
  "collabType": "brand_mention_only",
  "caption": "Chapter Four: The Return. #onyx #davidlaid #Gymshark @Belcast @Annabel Lucinda @Patty @James Beardwell @SWOLE @sawyer_klatt ",
  "views": 847000,
  "viewsVsFollowersPct": 12.8,
  "postsReadForCreator": 13,
  "brandPostsForCreator": 5,
  "disclosedBrandPosts": 0,
  "disclosureRatePct": 0,
  "brandPostViewsMedian": 110000,
  "ownPostViewsMedian": 105000,
  "brandPostViewGapPct": 4.8,
  "brandPostReachPerFollowerPct": 1.7,
  "ownPostReachPerFollowerPct": 1.6,
  "brandPostCount": 1,
  "brandIsRecurring": false,
  "whatIFound": "@gymshark names @Belcast in this caption and there is no ad marker on it. It has 847K views, and their brand posts land within a few percent of their own, so the audience treats them the same. For every 100 people following @gymshark, a post naming a brand reaches about 1.7 of them - that is the number to divide their rate by, and the one that compares to anyone else you are looking at. Across the 13 posts read here 5 name a brand and 5 of those carry no marker."
}
```

***

### How it works

1. **A handle becomes a set of posts.** Hand us `@handle` and we pull their recent posts with
   the real view and like counts attached, so you never assemble a list yourself.
2. **Every caption is read as written.** Ad markers, brand mentions and partnership tags are
   taken from the creator's own words, and the exact marker we found is returned so you can
   check it.
3. **A brand mention is checked against the account it belongs to.** A name in a caption is a
   display name, not a handle, and the two are not the same account. We only name a brand we can
   stand behind.
4. **The set is compared against itself.** Once the posts are in, the brand ones and the
   personal ones are split and their medians compared. That comparison is the whole point and it
   only exists because we read the set rather than one post.
5. **The sentence is written last**, from the fields on that row and nothing else.

***

### ❓ FAQ

#### What do I actually type in?

A TikTok handle. That is the fastest path and it gives you the comparison numbers, because those
need the whole set. If you already know the exact posts, paste their links instead.

#### Why does `disclosed` come back empty on some rows?

Because there is nothing to mark. A post that names no brand is not an unmarked ad, it is just a
post, and saying otherwise about a real person is not something we will do. Empty means the
question does not apply.

#### Can I tell a real partnership from a gift?

`brandIsRecurring` and `brandPostCount` are how. One appearance is a seeding drop or a tag.
The same brand across several posts in the same window is an arrangement, and that is the one
that breaks category exclusivity.

#### Does this work for checking my own account before a pitch?

That is one of the better uses of it. Run yourself, get `brandPostReachPerFollowerPct`, and go
into the call with the number a brand's own team would compute about you.

#### How do I compare two creators?

Run both in one go. Per-creator numbers are scoped to that creator, and
`brandPostReachPerFollowerPct` is built to be read across accounts of any size.

***

### 💬 Your feedback

Custom brand lists, category conflict rules or a larger pull: reapx at reapxdev@proton.me.

***

*Unofficial and not affiliated with any social platform. Reads what a creator published. Where a
value cannot be read it is returned as `null` with the reason beside it, never as a guess, and a
run that reads nothing is never charged for.*

reapx · reapx.dev · reapxdev@proton.me

# Actor input Schema

## `handlesOrPosts` (type: `array`):

Drop a creator and we go through everything they have posted lately - a TikTok <code>@handle</code>, or an Instagram one written as <code>instagram.com/handle</code> or <code>ig:handle</code>. Or paste links to the exact posts you want checked; Instagram posts and reels and TikTok videos all work. Mix them however you like. We read every caption exactly as the creator wrote it.

## `maxPosts` (type: `integer`):

A ceiling on the run, so a long list cannot surprise you. We stop after this many posts.

## Actor input object example

```json
{
  "handlesOrPosts": [
    "@lorengray"
  ],
  "maxPosts": 20
}
```

# Actor output Schema

## `items` (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 = {
    "handlesOrPosts": [
        "@lorengray"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("reapx/brand-collab-detector").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 = { "handlesOrPosts": ["@lorengray"] }

# Run the Actor and wait for it to finish
run = client.actor("reapx/brand-collab-detector").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 '{
  "handlesOrPosts": [
    "@lorengray"
  ]
}' |
apify call reapx/brand-collab-detector --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,reapx/brand-collab-detector"
        }
    }
}

```

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/Du5iH9XYIV3CiZj09/builds/qGHgfJEO7VbwxsOTe/openapi.json
