# RedNote Scraper & Data API — Private Beta (`gen_one_ai/rednote-scraper`) Actor

Public RedNote search, creator posts, profiles and comments with strict caps, normalized records, safe exports and explicit access errors.

- **URL**: https://apify.com/gen_one_ai/rednote-scraper.md
- **Developed by:** [Ivan Tech](https://apify.com/gen_one_ai) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$9.00 / 1,000 valid records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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

## RedNote Scraper & Data API

A TypeScript Apify Actor for bounded collection of publicly accessible Xiaohongshu/RedNote search cards, creator posts, profiles, and comments. It produces normalized records, a run manifest, a spreadsheet-safe CSV, and JSONL.

**Deployment:** [Open the private Actor in Apify](https://console.apify.com/actors/Wtu0aoLHIsk87MswG). The default run configuration pins a tested build, with 1,024 MB memory and a 900-second platform timeout; `docs/deployment.json` records the current build number. [The verified profile run](https://console.apify.com/actors/runs/vfAbgnllPW2JLofD3) returned one real record. On that collection-validation build, search and comments were access-restricted; creator posts returned an unrecognized source error. See `docs/cloud-smoke.json` for the exact outcomes and `docs/monetization.json` for later billing checks.

**Release: private engineering beta, with pay-per-event monetization configured at $9 per 1,000 valid unique records.** The collector uses a fresh anonymous Chromium context and reads the page's public state and its normal network responses. RedNote can require login or verification. Those targets return explicit errors; an empty dataset does not mean that the keyword or creator has no content. Reliable live coverage and commercial reuse have not been established. This independent service is not affiliated with RedNote.

Search and user posts are the MVP modes. Profiles and comments, including replies, are experimental. No direct full-post-detail mode is offered. No passwords, imported cookies, account pools, CAPTCHA solving, private content, posting, messaging, or media downloading are supported.

### Run on Apify

Open the Actor's Input tab and select one mode. Supply **only that mode's target array**. Example files are in `examples/`.

Search:

```json
{"mode":"search","keywords":["咖啡","AI productivity"],"maxItems":100,"maxItemsPerTarget":50,"schemaVersion":"1.0"}
```

Creator posts:

```json
{"mode":"userPosts","profileUrls":["https://www.xiaohongshu.com/user/profile/6784ba7f00000000080191fb"],"maxItems":50,"maxItemsPerTarget":50,"schemaVersion":"1.0"}
```

Profile (experimental):

```json
{"mode":"profile","profileUrls":["https://www.xiaohongshu.com/user/profile/6784ba7f00000000080191fb"],"maxItems":1,"schemaVersion":"1.0"}
```

Comments (experimental):

```json
{"mode":"comment","postUrls":["https://www.xiaohongshu.com/explore/670a46a50000000024017580"],"maxItems":20,"maxItemsPerTarget":20,"includeReplies":true,"maxRepliesPerComment":5,"schemaVersion":"1.0"}
```

These example URLs are public documentation examples, not availability guarantees. Replace them with the public targets you are authorized to analyze. Supported URL routes are `/user/profile/<24-character hex ID>`, `/explore/<ID>`, and `/discovery/item/<ID>`. HTTPS only; short links are rejected. Public share parameters `xsec_token` and `xsec_source` can be used for navigation but are removed from customer-facing records and the manifest. Apify retains the original input separately in its access-controlled run storage.

| Option | Meaning |
| --- | --- |
| `mode` | Required: `search`, `userPosts`, `profile`, or `comment` |
| `keywords` / `profileUrls` / `postUrls` | Exactly the relevant array, 1–20 targets before deduplication |
| `maxItems` | Global unique record cap; default 100, maximum 1,000 |
| `maxItemsPerTarget` | Unique records associated with each target; default 50, maximum 1,000 |
| `includeReplies` | Comment mode only; omitted means false |
| `maxRepliesPerComment` | Only with replies enabled; default 10, maximum 100 |
| `schemaVersion` | `1.0` |

Profile mode returns at most one record per target. Parents and replies both consume record caps. A post found under two keywords is exported once, with both associations in the manifest. Repeated targets are deduplicated without translating or lowercasing keywords. Unknown properties and irrelevant target arrays are rejected before collection.

The run uses one worker and one page at a time, at most three retries after transient navigation failures, a two-minute per-target collection deadline, and a fourteen-minute application deadline within the fifteen-minute platform timeout. Login, verification, and rate-limit responses are not repeatedly retried. Pagination uses the visible page; there is no guarantee of chronological order or exhaustive results. No date filtering is implemented.

### Read results correctly

1. Check `OUTPUT` and `MANIFEST` in the run's key-value store.
2. Read source records from the default dataset or `RESULTS.jsonl`.
3. For spreadsheets, download **`RESULTS.csv`** from the Output tab. It neutralizes formula-leading cells. The platform's raw dataset-to-CSV export does not apply this extra transformation.
4. Inspect `MANIFEST.targets`, query `associations`, `commentThreads`, and `RECONCILIATION` before interpreting coverage or charges.

`sourceCoverage` is always `unknown`. Reaching your requested cap can be a successful bounded run without proving comprehensive coverage.

| Product status | Interpretation |
| --- | --- |
| `succeeded` | Accessible collection ended or customer caps were reached without parser/access errors. A source-confirmed empty result can succeed. |
| `partial` | Some valid output exists, but access, pagination, parsing, time, or reconciliation prevented a clean completion. |
| `failed` | No useful results and an error. The Apify run is also marked FAILED. |
| `cancelled` | Application-observed graceful cancellation; completed records remain available. |
| `running` | In-progress manifest, not a final result. |

Apify execution status is separate. A partial product run exits normally so exports remain available. A hard platform abort can leave an earlier manifest; check the platform status as well. A resurrected run reuses its checkpoint and dataset. An uncertain publication or charge freezes admission for operator reconciliation instead of replaying the side effect.

Example empty outcome: `status: succeeded`, `uniqueRecords: 0`, target reason `ACCESSIBLE_END` with verified source evidence. Example blocked outcome: `status: failed`, target reason `ACCESS_RESTRICTED`, zero result charges. Example partial outcome: `status: partial`, some records, target reason `PAGINATION_UNAVAILABLE`. No blocked response is labeled as a genuine empty search.

Structured errors include `INVALID_INPUT`, `ACCESS_RESTRICTED`, `RATE_LIMITED`, `TARGET_UNAVAILABLE`, `PARSER_CHANGED`, `BUDGET_REACHED`, `TIMEOUT`, `NETWORK_ERROR`, `MODE_DISABLED`, `BILLING_UNCERTAIN`, `PUBLICATION_UNCERTAIN`, `CONFIGURATION_ERROR`, and `CANCELLED`.

### Field dictionary

| Fields | Contract |
| --- | --- |
| `recordType`, `sourceId`, `sourceUrl` | Record type, original string ID, canonical link without session/share tokens |
| `observedAt`, `runId`, `buildId` | UTC observation time and actual run/build provenance |
| `schemaVersion`, `parserVersion` | Output contract and extraction implementation versions |
| `fieldAvailability` | `available`, `not_provided_by_source`, `invalid_source_value`, `sanitized`, or `truncated` |
| `transformations` | Field-specific contact/token minimization, control-character removal, or truncation reasons |
| Post: `title`, `text`, `postType` | Source-provided values; unavailable content is null, never invented full text |
| Post/comment: `creator` | String creator ID, display name, canonical profile link when present |
| Post: `media` | Image descriptors only: URL, available dimensions, `isTemporary: true`; no download or durability promise |
| Post: `metrics` | Likes, comments, saves and shares, independently null when unavailable |
| Profile: `displayName`, `biography`, `avatarUrl` | Minimized public attributes; no age, gender, location, email or phone fields |
| Profile: `metrics` | Followers, following, and combined `likesAndCollections`; the aggregate is not split |
| Comment: `postId`, `parentCommentId`, `text` | Post association and reply hierarchy; parentCommentId is null for root comments |
| Comment: `repliesReported` | Source-reported count with precision metadata |
| `MANIFEST.commentThreads` | Actual `repliesCollected`, `repliesReported`, `repliesComplete` and a reason for incompleteness |
| `publishedAt` | `{raw, iso, precision, timezone}` or null. Relative/local source text keeps null ISO/timezone; epoch timestamps retain second/millisecond precision. |

Every metric is `{ "value": number|null, "raw": string|null, "isApproximate": boolean }`. `1.2万` maps to 12,000 with `isApproximate: true`. Missing values are null. All records pass a discriminated runtime schema before publication. The field mapping registry is exported from `src/normalize.ts`; only explicit reviewed source paths are accepted.

### Pricing and budget

The configured price is **$0.009 per valid unique record ($9 per 1,000)**, effective October 15, 2026. One custom event, `valid-record`, is the primary pricing unit. There is no startup fee or automatic dataset-item fee. Platform execution usage is included in the customer event price; operator/owner runs and post-run storage access can still incur Apify usage costs. The Actor remains private.

The minimum selectable maximum run budget is **$0.009**, enough for one result. This is a budget floor, not a minimum fee: a blocked or empty collection has zero result events. A 100-record cap permits at most $0.90 in result fees, subject to a lower launch spending cap. Pricing is configured but profitability is not established; Apify's initial small-sample usage estimate was $10.16 per 1,000 results, above the $7.20 remaining from $9 after the standard 20% marketplace share. See `docs/monetization.md`.

The paid path uses one custom event, `valid-record`, after confirmed dataset publication. A stable per-run/record idempotency key accompanies each charge. It refuses a paid configuration with additional nonzero event types and requires a finite `maxTotalChargeUsd` launch setting. Automatic dataset/start fees are removed. Duplicates, status rows, invalid inputs and failed collection are never intentionally charged. A later independent observation is a new record for billing purposes.

`MANIFEST.billing` exposes the maximum result charge, confirmed count, actual result charges or null if uncertain, and reconciliation. `actualResultChargesUsd` excludes compute/storage costs, which are shown on the Apify run. An in-flight operation may consume operator resources even after admission stops. See `docs/operations.md` for interruption and correction procedures.

### Native API

Use the asynchronous native Apify API. Keep credentials on your server and pin the tested build number from `docs/deployment.json`.

```sh
export APIFY_TOKEN='your-server-side-token'
node scripts/launch.mjs examples/search.json --build YOUR_TESTED_BUILD --max-charge-usd 0.25
```

The script requires an explicit spending cap and returns the run, dataset, and key-value-store IDs. Poll `GET /v2/actor-runs/{runId}`; fetch the manifest at `GET /v2/key-value-stores/{storeId}/records/MANIFEST`; retrieve results at `GET /v2/datasets/{datasetId}/items?format=json`. Send `Authorization: Bearer …` server-side. For graceful cancellation use `POST /v2/actor-runs/{runId}/abort?gracefully=true`.

Input `maxItems` is our record cap; the Apify run-query `maxItems` has separate platform semantics. Spending caps are launch options. There is no companion API, subscription dashboard, custom webhook sender, or 24-hour job submission idempotency service in this release; the PRD defers those until paying integrations justify them. Use native polling now. Avoid retrying an ambiguous job-launch request automatically.

### Development and deployment

```sh
npm ci
npm run check
npm run build
apify run --input-file examples/search.json
apify push --build-tag beta --wait-for-finish 60
```

Node.js 22 is required. Docker installs the Chromium version matching the pinned Playwright package. For local development, installed Google Chrome is used on macOS, or set `REDNOTE_BROWSER_EXECUTABLE` to an explicit browser path; alternatively run `npx playwright install chromium`. `REDNOTE_ENABLED_MODES=search,userPosts` independently disables other adapters. Never put Apify tokens in source files or client-side code.

The browser can connect only to HTTPS RedNote/CDN hosts through a local validating proxy. Every tunnel resolves DNS, rejects nonpublic answers and connects to the vetted address. Browser routes also check redirects, block downloads/media and disallow WebSockets and service workers. Raw HTML, screenshots, imported sessions and raw API response diagnostics are not retained.

The fixture suite tests contracts, normalization, SSRF controls, deduplication, caps, missing fields, reply relationships, publication/charge failure boundaries and restarts. These fictional fixtures are **not** the PRD's 200 annotated live fixtures. See `docs/validation.md` for observed test results and outstanding commercial gates, and `docs/access-review.md` for source restrictions.

### Retention and responsible use

Direct customers own their Apify storage and control its deletion/retention. The Actor does not promise to delete their data seven days later. Operator-managed validation runs can use `scripts/retention.mjs` to remove this Actor's datasets and key-value stores after seven days. It defaults to a dry run, scopes by Actor ID and supports whole-dataset deletion; see operations documentation. No raw diagnostics are retained, so there is no hidden 24-hour raw-data store.

Use the data only for legitimate public-content research you are authorized to perform. Private-data collection, contact harvesting, harassment, sensitive-person profiling and deanonymization are outside this product's scope. Public visibility does not establish redistribution rights. Missing observations do not establish deletion, and likes are not a measure of sales, reach or sentiment.

For this private beta, corrections and takedowns go to the operator who supplied access (Apify account `Gen_One_AI`), with the run ID and source IDs. Commercial release requires a published support channel. Operators must record affected datasets and downstream exports, delete whole datasets or replace them with sanitized datasets, and notify affected recipients. Downloaded files cannot be recalled automatically.

# Actor input Schema

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

Search and user posts are the MVP modes. Profile and comment adapters are experimental in this private beta.

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

Search mode only. Up to 20 terms. Chinese, punctuation and emoji are preserved. Remove this field in other modes.

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

userPosts or profile mode only. Up to 20 HTTPS www.xiaohongshu.com/user/profile/<24-character ID> URLs. Public share-link tokens are accepted for navigation but excluded from output.

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

comment mode only. Up to 20 HTTPS www.xiaohongshu.com/explore/<24-character ID> or /discovery/item/<ID> URLs. Short URLs are not supported.

## `maxItems` (type: `integer`):

Across all targets. Replies consume this cap. This differs from the platform run-query maxItems option.

## `maxItemsPerTarget` (type: `integer`):

Includes shared records and replies. Profile mode always returns at most one record per target.

## `includeReplies` (type: `boolean`):

Comments only. Omit this property in other modes. Default false.

## `maxRepliesPerComment` (type: `integer`):

Only with includeReplies=true. Defaults to 10. Global and target caps still apply.

## `schemaVersion` (type: `string`):

Explicit canonical output version.

## Actor input object example

```json
{
  "mode": "search",
  "keywords": [
    "咖啡"
  ],
  "maxItems": 100,
  "maxItemsPerTarget": 50,
  "schemaVersion": "1.0"
}
```

# Actor output Schema

## `records` (type: `string`):

Unique source records only; errors and summaries are separate. An empty dataset can mean blocked access: always read the manifest.

## `manifest` (type: `string`):

Product status, per-target stopping reasons, query associations, reply completeness and billing reconciliation.

## `csv` (type: `string`):

UTF-8 CSV with formula-leading cells escaped. Prefer this file to a raw dataset CSV export.

## `jsonl` (type: `string`):

One canonical record per line.

## `reconciliation` (type: `string`):

Accepted and charged totals, uncertainty and correction records.

# 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 = {
    "mode": "search",
    "keywords": [
        "咖啡"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("gen_one_ai/rednote-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 = {
    "mode": "search",
    "keywords": ["咖啡"],
}

# Run the Actor and wait for it to finish
run = client.actor("gen_one_ai/rednote-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 '{
  "mode": "search",
  "keywords": [
    "咖啡"
  ]
}' |
apify call gen_one_ai/rednote-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,gen_one_ai/rednote-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/Wtu0aoLHIsk87MswG/builds/jDO8XzPJQlQZgzVVQ/openapi.json
