# Lemon8 Media Scraper: Images, Videos & Downloads (`abotapi/lemon8-media-scraper`) Actor

Lightweight Lemon8 scraper for extracting image and video URLs from posts, with optional media downloads. Built for fast, clean media collection without comments, captions, or other post details.

- **URL**: https://apify.com/abotapi/lemon8-media-scraper.md
- **Developed by:** [Abot API](https://apify.com/abotapi) (community)
- **Categories:** Social media, Videos, Automation
- **Features:** Limited permissions
- **Stats:** 9 total users, 2 monthly users, 96.7% runs succeeded, 1 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 results

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

## Lemon8 Media Scraper: Image and Video URLs from Any Post

Lemon8 Media Scraper turns any Lemon8 post, or a batch of posts found by keyword, into a clean feed of image and video URLs. Paste post links or set a search term and region, and get every media URL a post carries, already upgraded to its higher-quality variant, with an option to download the actual files into the run's storage. Schedule it and turn on incremental mode to get only new and changed posts on every run, then export the results as JSON, CSV or Excel, or read them through the API.

### Why This Scraper?

- **Two ways to find posts.** Paste exact Lemon8 post links, or search by keyword and region through Lemon8's discovery page.
- **Clean media, nothing else.** Every image and video URL from a post, with thumbnails converted to their higher-quality variant by default.
- **Optional file download.** Pull the actual image and video files into the run's storage, not just links, when you need copies rather than references.
- **Any Lemon8 region.** Point it at the region code for the market you care about, the same way the Lemon8 app itself is localized.
- **Built for schedules.** Turn on incremental mode and recurring runs return only new and changed posts, not the same posts every time.
- **Resume without re-paying.** Continue an interrupted pull by pasting its run or dataset id, and posts already collected there are skipped.
- **Pipe results into your apps.** Send a summary of every record to Notion, Linear or Airtable through Apify's connectors, no extra script needed.

### Use Cases

- **Content archiving:** save images and videos from posts before they are edited or taken down.
- **Dataset building:** collect batches of images and videos for a training or research dataset.
- **Visual trend research:** track the styles, colors and formats trending in a niche or region.
- **Competitor and creator monitoring:** watch an account's or a keyword's posts on a schedule and catch new content as soon as it is up.
- **Personal backup:** pull your own account's post media out of Lemon8 into your own storage.

### Data You Get

> Sample shape: values are illustrative placeholders, not from a live record.

| Field | Example |
| --- | --- |
| `postId`, `groupId` | `"7543408422994264589"` |
| `postUrl`, `url` | `"https://www.lemon8-app.com/@username/7543408422994264589?region=us"` |
| `title`, `content` | post title and text without HTML |
| `articleClass` | post type, for example Gallery or Video |
| `likes`, `saves`, `commentCount` | engagement counts |
| `authorUserId`, `authorUsername`, `authorNickname` | the author |
| `authorAvatar`, `authorFollowers` | author avatar and follower count |
| `hashtags` | hashtags on the post |
| `topComments` | up to 5 comments from the post page (text, likes, replies, author, time) |
| `imageCount`, `videoCount` | `2`, `0` |
| `images` | array of image objects, one per extracted image |
| `images[].url` | `"https://example-cdn.lemon8-app.com/sample-image-1.webp"` |
| `images[].alt`, `images[].type` | `""`, `"image"` |
| `images[].keystoreKey` | `"7543408422994264589_img_0.webp"` (only when `downloadMedia` is on) |
| `videos` | array of video objects, one per extracted video |
| `videos[].url`, `videos[].poster` | video file and its poster image |
| `videos[].keystoreKey` | `"7543408422994264589_vid_0.mp4"` (only when `downloadMedia` is on) |
| `videoUrl`, `thumbnailUrl` | video file (video posts only) and cover image |
| `keyword`, `category`, `region` | how the post was reached |
| `sourceMode`, `scrapedAt` | mode used and scrape time |
| `changeType` | `"NEW"` (incremental mode only) |
| `changedFields` | `["videos"]` (incremental mode only) |
| `firstSeenAt`, `lastSeenAt` | `"2026-07-31T03:00:00Z"` (incremental mode only) |

The post detail fields come from the same post page as the media, so they add no extra requests. In incremental mode the counts, comments, follower count, `scrapedAt` and the source fields are returned but never make a post `UPDATED`.

The last four fields (`changeType`, `changedFields`, `firstSeenAt`, `lastSeenAt`) are added only when `incrementalMode` is on. A normal run's rows carry the media fields and the post detail fields above, nothing more.

#### Output views

The dataset has these views in the Console: **Media Overview** (title, author, engagement and counts), **Images**, **Videos**, **Post Details** (text, hashtags, comments, source fields) and **Changes** (incremental fields). Every field is declared in the dataset schema, so exports have stable columns.

### How to Use

1. Choose your input: paste specific post URLs, or set a search term and region to find posts by keyword.
2. Pick which media to keep (images, videos, or both), and whether to only collect links or also download the files.
3. Set **Limit** to control how many posts are processed and how much the run costs, then click **Start**.
4. Read the results from the dataset, or download them as JSON, CSV or Excel.

**Search discovery in one region:**

```json
{
  "searchTerm": "skincare",
  "region": "us",
  "limit": 20,
  "saveImages": true,
  "saveVideos": true
}
```

**Direct post URLs with file download:**

```json
{
  "postUrls": [
    "https://www.lemon8-app.com/@username/7543408422994264589",
    "https://www.lemon8-app.com/@another/7550145067198005791"
  ],
  "downloadMedia": true
}
```

**Recurring monitoring with incremental mode:**

```json
{
  "searchTerm": "streetwear",
  "region": "sg",
  "limit": 50,
  "incrementalMode": true,
  "stateKey": "streetwear-sg-watch"
}
```

**Resume an interrupted pull:**

```json
{
  "searchTerm": "travel",
  "region": "au",
  "limit": 100,
  "resumeFromRunId": "<previous run or dataset id>"
}
```

#### Modes

| Mode | Input used | What you get |
|---|---|---|
| `auto` (default) | `postUrls`, else `searchTerm` | Your own URLs if you set any. The example `postUrls` run only when you set none of the search or saved-task inputs listed under "Default changed" below; otherwise a keyword search. |
| `post` | `postUrls` | The posts you list. |
| `discover` | `keywords`, `region` | About 50 posts per keyword, in order, up to the cap. |
| `profile` | `profileUrls` | Posts linked from each profile page or @username. |
| `feed` | `feedCategories` | First page of each category feed, about 10 posts each. |

#### Run it from your code

Python:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("abotapi/lemon8-media-scraper").call(run_input={"searchTerm": "fashion", "region": "us", "limit": 20})
for post in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(post["postId"], post["imageCount"], post["videoCount"])
```

JavaScript:

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

const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' });
const run = await client.actor('abotapi/lemon8-media-scraper').call({ searchTerm: 'fashion', region: 'us', limit: 20 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

Or connect it to Make, Zapier, n8n, Google Sheets or webhooks from the **Integrations** tab.

#### Resume and recurring updates

- **Resume** (`resumeFromRunId`) points this run at a previous run or dataset id so the actor knows which posts it already saw there.
- **Incremental mode** (`incrementalMode`) is for scheduled runs over the same posts or search. Each row is classified `NEW`, `UPDATED` (with `changedFields`), or `UNCHANGED` (suppressed and not billed unless `emitUnchanged` is on). `EXPIRED` is never detected unless `emitExpired` is on, and even then only once a run has scanned every tracked post to its natural end; a capped run or a resumed run skips EXPIRED detection that time rather than risk a wrong result. `REAPPEARED` shows up only when a post previously marked `EXPIRED` by such a run is found again on a later run, it does not appear on a first run or under normal `NEW`/`UPDATED` conditions. **Ignore fields when detecting changes** (`ignoreFieldsForChanges`): list field names, or a dotted path such as `images.alt` for a key inside every item of a list, that must not make a post UPDATED or appear in `changedFields`. The fields stay in the output; changing the list never re-marks your saved baseline as updated. Rotating CDN signatures and hostnames in image and video links are already ignored automatically.
- `stateKey` names or shares the stored state. Leaving it empty derives a key from the mode and its inputs (`postUrls`/`searchTerm`, `keywords`, `profileUrls` or `feedCategories`), `region` and the media options, so two differently configured runs never mix state. Raising `limit` alone never starts a new baseline.
- Cosmetic link changes (a different delivery host, or a refreshed access token on an otherwise identical image or video) are normalized before comparing posts, so a post whose media has not actually changed is never marked `UPDATED` just because its link was reissued.
- With incremental mode off, output is exactly as in a normal run.

#### URL Mode

`postUrls` accepts these URL shapes (tracking parameters such as `utm_source` are ignored):

| Shape | Example | Behaviour |
|---|---|---|
| Post detail | `https://www.lemon8-app.com/@05luvlee/7535454724489986574?region=us` | Scrapes that post. `?region=` is optional. |
| Discovery (`/discover/<term>`, older path) | `https://www.lemon8-app.com/discover/fashion?region=us` | Lists posts for that keyword, up to Limit. |
| Discovery (`/experience/<term>`) | `https://www.lemon8-app.com/experience/fashion?region=us` | Same as above. |
| Discovery in another region | `https://www.lemon8-app.com/discover/fashion?region=jp` | The region is taken from the URL. |
| Feed (`/feed/<category>`) | `https://www.lemon8-app.com/feed/food` | Scrapes the first page of that category feed (about 10 posts). |
| Profile page | `https://www.lemon8-app.com/@05luvlee?region=us` | Scrapes the posts linked from the profile page, up to Limit. |
| Topic page (`/topic/<id>`) | `https://www.lemon8-app.com/topic/6880245568946880518?region=us` | Scrapes the posts linked from the topic page, up to Limit. `sourceMode` is `topic`. |

The `postUrl` field of a post URL you pass is returned exactly as you typed it (a relative path gets the site host added). If none of your URLs is supported, the run ends with the status message `No supported Lemon8 URL in postUrls`.

Anything else (`/tag/`, `/hashtag/`, `/search?q=`, a bare numeric id, or the `v.` and `m.` hosts) is skipped and logged as a warning that starts with `WARNING: unsupported URL skipped`.

**Default changed:** `postUrls` now ships with one example URL per shape above. In `auto` mode the examples run only when you leave the list untouched and set none of the following: a `searchTerm` other than "fashion", `keywords`, `profileUrls`, `incrementalMode`, `stateKey`, `resumeFromRunId`, or a `region` other than `us`. If you set any of those and leave the example list as is, the examples are dropped: keywords or a custom search term run a keyword search, `profileUrls` scrape those profiles, and the other settings run the keyword search for `searchTerm` in your `region`, so saved tasks and schedules that never sent `postUrls` keep their earlier behavior and their saved incremental state. To search by keyword with no other setting, clear the list. `postUrls` always runs when it holds your own URLs.

### Send results into your apps (MCP connectors)

Optionally pipe the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. This is an extra delivery step after the scrape: the Apify dataset is never changed.

**What gets written to the connector:** a condensed, human-readable summary of each record, not the full JSON. Each item becomes one entry with a title and its key fields flattened to plain text. The complete record always stays in the Apify dataset.

1. Authorize a connector once under **Apify > Settings > Integrations** (Notion, Linear, Airtable, or Apify).
2. Select it in the **"Pipe results into your apps"** input field. If the picker is empty, you haven't authorized a connector yet.
3. For **Notion**, also set `notionParentPageUrl` to the page where items should be created.

The connection is mediated by Apify's MCP proxy, so this actor never sees your third-party credentials. Leave the field empty to skip.

### Input Parameters

| Parameter | Type | Default | Description |
| --- | --- | --- | --- |
| `postUrls` | array | five example URLs (one per supported shape) | Lemon8 URLs to scrape, see URL Mode above. When this holds your own URLs, `searchTerm` is ignored; the example list is dropped in the cases listed under "Default changed". |
| `searchTerm` | string | `"fashion"` | Search keyword used to find posts on Lemon8's discovery page. Used when `postUrls` is empty, when the example `postUrls` are dropped (see "Default changed"), and in discover mode when Keywords is empty. |
| `mode` | string | `"auto"` | `auto` (your own `postUrls` if set, else `searchTerm`; see "Default changed"), `post`, `discover` (keywords), `profile` (profile URLs) or `feed` (category feeds). A chosen mode uses only its own input. |
| `keywords` | array | none | Keywords for discover mode, about 50 posts each, taken in order up to the cap. Also used in `auto` mode once `postUrls` is cleared. |
| `profileUrls` | array | none | Profile pages or @usernames for profile mode. |
| `feedCategories` | array | `["foryou"]` | Feed mode: foryou, food, beauty, fashion, home, fitness, health, relationship, travel, photography, pet, reading, cars, outdoors (first page of each, about 10 posts). A `/feed/<category>` URL in `postUrls` works too. |
| `region` | string | `"us"` | Region code passed to Lemon8 (for example `us`, `au`, `nz`, `jp`, `th`, `id`, `vn`, `my`, `sg`, `ca`). This is a free-text field, not a fixed list. |
| `limit` | integer | `2` | Maximum number of posts to return, from 1 to 1000. |
| `saveImages` | boolean | `true` | Extract image URLs from each post. |
| `saveVideos` | boolean | `true` | Extract video URLs from each post. |
| `downloadMedia` | boolean | `false` | Also download each image and video file into the run's key-value store, increasing run time and storage. |
| `highQuality` | boolean | `true` | Convert thumbnail image URLs to their higher-quality variant. |
| `proxy` | object | none (prefill shown in the console: a managed connection for country US) | Connection settings for requests. When the managed connection is turned on, the actor also keeps an alternate connection in reserve and switches to it automatically if a request is refused twice in a row. Leaving it off sends requests directly, with no automatic switch. |
| `resumeFromRunId` | string | none | Paste a previous run or dataset id to seed this run with the posts it already found there. |
| `incrementalMode` | boolean | `false` | Turn on for daily or recurring monitoring, so a run returns only new and changed posts against the previous run. |
| `stateKey` | string | none | Name a monitoring campaign explicitly, or leave empty to derive one automatically from the mode and its inputs (`postUrls`/`searchTerm`, `keywords`, `profileUrls` or `feedCategories`), `region` and the media options. |
| `emitUnchanged` | boolean | `false` | Also return (and bill) posts that have not changed since the last run, marked `UNCHANGED`. |
| `emitExpired` | boolean | `false` | Also return (and bill) posts that were present before but are no longer found, marked `EXPIRED`, once a run fully scans every tracked post. |
| `ignoreFieldsForChanges` | array | none | Incremental mode: field names or dotted paths (for example `images.alt`) that never make a post `UPDATED`. The fields stay in the output. |
| `mcpConnectors` | array | none | Optional: send a summary of each record to apps you authorized under Integrations. |
| `notionParentPageUrl` | string | none | Notion connector only: page under which item pages are created. |
| `maxNotifyListings` | integer | `50` | Cap on items written to each connector per run, from 1 to 1000. Does not affect the dataset. |

### Output Example

> Sample shape: values are illustrative placeholders, not from a live record.

```json
{
  "postId": "7543408422994264589",
  "postUrl": "https://www.lemon8-app.com/@username/7543408422994264589?region=us",
  "images": [
    {
      "url": "https://example-cdn.lemon8-app.com/sample-image-1.webp",
      "alt": "",
      "type": "image"
    }
  ],
  "videos": [
    {
      "url": "https://example-cdn.lemon8-app.com/sample-video-1.mp4",
      "poster": "https://example-cdn.lemon8-app.com/sample-poster-1.webp",
      "type": "video"
    }
  ],
  "imageCount": 1,
  "videoCount": 1
}
```

### Plan Requirement

On paid Apify plans the default datacenter connection works and no residential proxy plan is needed: Lemon8 pages loaded in testing through Apify datacenter proxies on a paid account. The only block seen was a rare one-off HTTP 429 on a post link carrying extra tracking parameters, which the actor retries on the same connection. Apify free plans do not include Apify proxy access at all, so on a free plan the actor logs a notice and continues on a backup connection, and fails with a clear message if no page can be loaded.

The default proxy input uses the managed connection for the selected region. If a request is refused twice in a row, the actor switches to an alternate connection and logs: "Served over the backup connection (the default connection was unavailable)." If the connection is refused for the account, the run logs a notice that Apify free plans do not include residential proxies and continues on the backup connection. If no page can be read at all, the run fails with a clear message (for example "Could not load any results page (the requests failed), so nothing was collected. This is a connection problem, not an empty search.") instead of returning an empty success. Leaving the proxy input off sends requests directly.

### FAQ

#### How much does it cost?

Pricing is pay per event: one **actor start** event per run (`apify-actor-start`) and one **dataset item** event per row pushed to the dataset (`apify-default-dataset-item`). The **Pricing** tab on the actor page shows the current rates for your plan. Nothing else is charged: no extra fee for comments, downloads or modes, and suppressed `UNCHANGED` rows in incremental mode are not billed. Use **Limit** (hard cap 1000 per run) to cap the size, and therefore the cost, of any run.

#### Is it legal to scrape Lemon8?

This actor collects only media URLs from publicly viewable Lemon8 posts. You are responsible for how you use the results: follow Lemon8's terms and the laws that apply to you, and get legal advice if you plan commercial redistribution. The images and videos themselves may carry their own copyright or other rights held by the original poster.

#### Can I get only new or changed posts on a schedule?

Yes. Schedule the actor from the **Schedules** tab and turn on **Incremental mode**. Each run then returns only new and changed posts, and unchanged ones are not billed unless you turn that on too.

#### Why did a run return no items instead of some media?

If the given post URLs could not be read (deleted, private, or an unrecognized page), or a search and region combination matched nothing, the run stops with a clear status message and returns nothing rather than a placeholder row. This is a normal, successful outcome, not a failure.

#### Why might a run fail instead of returning an empty result?

A run fails outright only when `resumeFromRunId` points to a run or dataset that cannot be read (a bad or inaccessible id), or when `incrementalMode` is combined with `resumeFromRunId` while saved incremental state already exists for that state key (remove one or set a different `stateKey`). Individual posts that fail to load are skipped and logged, they don't fail the whole run.

#### Can I use it with AI agents or MCP?

Yes. Call it from any Apify integration or MCP client, and use the connector field to push results into Notion, Linear or Airtable.

### 🔗 Want more Lemon8 data?

Pair this actor with these related scrapers from the same team:

<table>
<tr><td>📱 <a href="https://apify.com/abotapi/lemon8-feeds-scraper"><b>Lemon8 Feed Scraper</b></a><br>Scrape Lemon8 feeds across 22 categories and 10+ regions. Extract posts, images, videos...</td><td>📱 <a href="https://apify.com/abotapi/lemon8-profile-scraper"><b>Lemon8 Profile Scraper</b></a><br>Scrape Lemon8 user profiles with automated multi-profile discovery. Extract profile data...</td></tr>
<tr><td>📱 <a href="https://apify.com/abotapi/lemon8-discover-scraper"><b>Lemon8 Search Scraper</b></a><br>Scrape Lemon8 posts by keyword across multiple regions. Extract posts, images, videos...</td><td>📱 <a href="https://apify.com/abotapi/universal-media-extractor"><b>Universal Media Extractor</b></a><br>Extract videos, audio, and metadata from 1000+ websites including YouTube, TikTok...</td></tr>
<tr><td>📱 <a href="https://apify.com/abotapi/reddit-scraper"><b>Reddit⚡ Lightning</b></a><br>Scrape Reddit posts, comments, and media from any subreddit or user profile - no login or...</td><td>📱 <a href="https://apify.com/abotapi/truthsocial-com-scraper"><b>Truth Social Scraper</b></a><br>Scrape public Truth Social profiles, posts, media, metrics, author data, and complete...</td></tr>
</table>

👉 [Browse all abotapi scrapers](https://apify.com/abotapi)

### 💬 Support

- 🐞 **Found a bug or a missing field?** Open a ticket on the [Issues tab](https://apify.com/abotapi/lemon8-media-scraper/issues/open). We usually reply within hours.
- 🛠️ **Need another site, extra fields or a private build?** Email <contact@abotapi.com>.
- ⭐ **Enjoying it?** A quick review on the actor page helps other users find it.

# Actor input Schema

## `mode` (type: `string`):

What to scrape. auto (default): your own Post URLs if set, otherwise the Search Term. The example Post URLs run only when you set none of: a Search Term other than fashion, Keywords, Profile URLs, Incremental mode, State key, Resume, or a region other than us. post: the Post URLs list. discover: Keywords (or Search Term). profile: Profile URLs. feed: Feed categories. In a chosen mode only that mode's input is used.

## `postUrls` (type: `array`):

Lemon8 URLs to scrape: post pages (/@user/<id>), discovery pages (/discover/<term> or /experience/<term>, region taken from ?region=), profile pages (/@user, lists the profile's posts) and topic pages (/topic/<id>). Tracking parameters are ignored. Unsupported URLs are skipped with a warning. When postUrls holds your own URLs, searchTerm is ignored. The example list is dropped when you set a different Search Term, Keywords, Profile URLs, Incremental mode, State key, Resume or a region other than us (clear the list to search by keyword).

## `searchTerm` (type: `string`):

Search keyword to find posts on Lemon8 discovery page. Used only if postUrls is empty (or still holds the example list while you set a different keyword).

## `feedCategories` (type: `array`):

Category feeds to read in feed mode (the first page of each, about 10 posts). Defaults to For you when empty.

## `keywords` (type: `array`):

Keywords to search on Lemon8 in discover mode (about 50 posts per keyword). Also used in auto mode when Post URLs is cleared. The Limit / Max results cap applies in order across keywords.

## `profileUrls` (type: `array`):

Lemon8 profile pages or @usernames to list in profile mode (the posts shown on the profile page).

## `region` (type: `string`):

Region code for Lemon8. Common codes: us, uk, jp, th, id, ph, vn, my, sg (any other code, e.g. au, is passed through as typed).

## `limit` (type: `integer`):

Maximum number of posts to return (1 to 1000 per run)

## `saveImages` (type: `boolean`):

Extract image URLs from posts

## `saveVideos` (type: `boolean`):

Extract video URLs from posts

## `downloadMedia` (type: `boolean`):

Download media files to key-value store (increases run time and storage)

## `highQuality` (type: `boolean`):

Convert thumbnail URLs to high quality versions

## `proxy` (type: `object`):

Proxy for requests. Defaults to Apify datacenter (US); if the datacenter tunnel is unavailable or gets blocked, the actor automatically falls back to a backup proxy pool.

## `resumeFromRunId` (type: `string`):

Paste a previous run ID or dataset ID to continue a large pull of posts without returning or charging for posts already collected there. Use this after an interrupted run, or when continuing a pull in another run. For recurring monitoring of the same posts/search, use Incremental mode below instead.

## `incrementalMode` (type: `boolean`):

Turn this on for daily or recurring monitoring. The first run returns all matching posts as NEW. Later runs normally return only NEW, UPDATED, and REAPPEARED posts. Turn on "Emit unchanged" or "Emit expired" only when you also want those posts returned (and billed). State is kept separately for each postUrls/searchTerm, region, and media-option combination; use State key when you want to name or deliberately share a monitoring campaign. To continue one specific interrupted run instead, use Resume from a previous run above.

## `stateKey` (type: `string`):

Optional. Name this monitoring campaign to keep its state stable, or to deliberately share state across differently-configured runs. Leave empty to let the actor derive a key automatically from the mode and its inputs (postUrls/searchTerm, keywords, profileUrls or feedCategories), region, and the media options — different setups then never mix state with each other.

## `emitUnchanged` (type: `boolean`):

Off by default. Turn on to also return posts that have not changed since the last run, marked UNCHANGED. This returns — and bills — extra rows you already have, so leave it off unless you specifically want the full snapshot every run.

## `emitExpired` (type: `boolean`):

Off by default. Turn on to also return posts that were present in a previous run but are no longer found, marked EXPIRED. Only produced once a run has fully scanned every tracked post — not when Limit capped it, or when Resume was used. This returns — and bills — extra synthetic rows, so leave it off unless you need expiry tracking.

## `ignoreFieldsForChanges` (type: `array`):

Optional. Field names that must not make a post UPDATED or appear in Changed fields, for example imageCount when you do not care about that movement. Use a dotted path such as images.alt to ignore one key inside every item of a list. The fields still appear in the output; they are only left out of the comparison. Changing this list never marks the whole baseline as updated.

## `mcpConnectors` (type: `array`):

Optionally send the scraped results into the apps you already use, via Model Context Protocol (MCP) connectors. Authorize a connector once under Apify → Settings → Integrations, then select it here. The connector receives a condensed, human-readable summary per item (title + key fields), not the full JSON — the complete record stays in the dataset. Leave empty to skip. Supported: Notion (https://mcp.notion.com/mcp), Linear (https://mcp.linear.app/sse), Airtable (https://mcp.airtable.com/mcp), Apify (https://mcp.apify.com).

## `notionParentPageUrl` (type: `string`):

URL (or id) of the Notion page under which item pages are created. Required to enable the Notion export; ignored by other connectors.

## `maxNotifyListings` (type: `integer`):

Cap on items written to each connector per run. Does not affect the dataset.

## Actor input object example

```json
{
  "mode": "auto",
  "postUrls": [
    "https://www.lemon8-app.com/@05luvlee/7535454724489986574?region=us",
    "https://www.lemon8-app.com/discover/fashion?region=us",
    "https://www.lemon8-app.com/experience/fashion?region=us",
    "https://www.lemon8-app.com/discover/fashion?region=jp",
    "https://www.lemon8-app.com/@05luvlee?region=us"
  ],
  "searchTerm": "fashion",
  "feedCategories": [
    "foryou"
  ],
  "keywords": [
    "fashion"
  ],
  "profileUrls": [
    "https://www.lemon8-app.com/@05luvlee"
  ],
  "region": "us",
  "limit": 2,
  "saveImages": true,
  "saveVideos": true,
  "downloadMedia": false,
  "highQuality": true,
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyCountry": "US"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": [],
  "maxNotifyListings": 50
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `images` (type: `string`):

No description

## `videos` (type: `string`):

No description

## `details` (type: `string`):

No description

## `changes` (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 = {
    "postUrls": [
        "https://www.lemon8-app.com/@05luvlee/7535454724489986574?region=us",
        "https://www.lemon8-app.com/discover/fashion?region=us",
        "https://www.lemon8-app.com/experience/fashion?region=us",
        "https://www.lemon8-app.com/discover/fashion?region=jp",
        "https://www.lemon8-app.com/@05luvlee?region=us"
    ],
    "feedCategories": [
        "foryou"
    ],
    "keywords": [
        "fashion"
    ],
    "profileUrls": [
        "https://www.lemon8-app.com/@05luvlee"
    ],
    "proxy": {
        "useApifyProxy": true,
        "apifyProxyCountry": "US"
    },
    "incrementalMode": false,
    "emitUnchanged": false,
    "emitExpired": false,
    "ignoreFieldsForChanges": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("abotapi/lemon8-media-scraper").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 = {
    "postUrls": [
        "https://www.lemon8-app.com/@05luvlee/7535454724489986574?region=us",
        "https://www.lemon8-app.com/discover/fashion?region=us",
        "https://www.lemon8-app.com/experience/fashion?region=us",
        "https://www.lemon8-app.com/discover/fashion?region=jp",
        "https://www.lemon8-app.com/@05luvlee?region=us",
    ],
    "feedCategories": ["foryou"],
    "keywords": ["fashion"],
    "profileUrls": ["https://www.lemon8-app.com/@05luvlee"],
    "proxy": {
        "useApifyProxy": True,
        "apifyProxyCountry": "US",
    },
    "incrementalMode": False,
    "emitUnchanged": False,
    "emitExpired": False,
    "ignoreFieldsForChanges": [],
}

# Run the Actor and wait for it to finish
run = client.actor("abotapi/lemon8-media-scraper").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 '{
  "postUrls": [
    "https://www.lemon8-app.com/@05luvlee/7535454724489986574?region=us",
    "https://www.lemon8-app.com/discover/fashion?region=us",
    "https://www.lemon8-app.com/experience/fashion?region=us",
    "https://www.lemon8-app.com/discover/fashion?region=jp",
    "https://www.lemon8-app.com/@05luvlee?region=us"
  ],
  "feedCategories": [
    "foryou"
  ],
  "keywords": [
    "fashion"
  ],
  "profileUrls": [
    "https://www.lemon8-app.com/@05luvlee"
  ],
  "proxy": {
    "useApifyProxy": true,
    "apifyProxyCountry": "US"
  },
  "incrementalMode": false,
  "emitUnchanged": false,
  "emitExpired": false,
  "ignoreFieldsForChanges": []
}' |
apify call abotapi/lemon8-media-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,abotapi/lemon8-media-scraper"
        }
    }
}
```

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/cXSgNkaph4Oa2t7fz/builds/08GbmREkA44Zeet2Y/openapi.json
