# Bluesky Keyword & Mention Alerts — Only New Posts (`uplifted_novice_vbl/bluesky-keyword-mention-alerts`) Actor

Monitor Bluesky for keywords, mentions & hashtags on a schedule. Charges only for new posts. No login required.

- **URL**: https://apify.com/uplifted\_novice\_vbl/bluesky-keyword-mention-alerts.md
- **Developed by:** [Telemark Digital](https://apify.com/uplifted_novice_vbl) (community)
- **Categories:** Social media, Automation, Developer tools
- **Stats:** 2 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 new-posts

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

## What's an Apify Actor?

Actors are a software tools running on the Apify platform, for all kinds of web data extraction and automation use cases.
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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp.md).

If your project is in a different language, use 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

**Monitor Bluesky for keywords, mentions & hashtags on a schedule — and pay only for posts you haven't already seen.** SKYLARK turns an Apify Schedule into a cheap, recurring alert feed for [Bluesky](https://bsky.app) (the AT Protocol network): every run returns **only the new matched posts** it has never delivered to your task before, fires a **webhook** when something new appears, and needs **no Bluesky login** for keyword, hashtag, and mention monitoring.

> **Unofficial and independent.** This Actor is not affiliated with, endorsed by, or sponsored by Bluesky Social PBC.

> **If you're polling a stateless scraper ~250×/day to catch new posts, this is your bill cut.** You stop paying to re-download and re-deduplicate the same posts every hour.

### What does Bluesky Keyword & Mention Alerts do?

- **Watches keywords, phrases, hashtags, and handles** (a handle is watched two ways — its own new posts _and_ new posts that mention it), all in **one task, one merged deduplicated feed**.
- **Charges only for genuinely new posts.** Duplicates, excluded terms, and off-language posts are filtered _before_ billing. Seen IDs are retained for 30 days, subject to the documented state cap.
- **Runs headless on a Schedule** and pushes structured JSON to a dataset (download as JSON, CSV, Excel, or HTML) + an optional webhook payload for Slack / n8n / Make / any endpoint / an AI agent.

#### How much does it cost to monitor Bluesky? (honest, maxItems-aware)

| Your watch                                            | SKYLARK (only-new)           | A stateless scraper, re-scraped hourly                                     |
| ----------------------------------------------------- | ---------------------------- | -------------------------------------------------------------------------- |
| Quiet-to-moderate term (~0.3–2 new posts/run), hourly | **~$2–6 / month**            | ~$10.70–26.80 / month (capped at 10–25 posts/run) + you dedupe it yourself |
| Hot, all-new firehose term (100s new/run)             | **~$3.00 / 1,000 new posts** | **~$1.49 / 1,000** — _cheaper per post; use a per-result scraper for this_ |

We are honest about the trade: on a **quiet-to-moderate recurring watch — the actual monitoring use case — SKYLARK is 3–5× cheaper per month** and hands you deduped, webhook-ready alerts with no downstream plumbing. On a **hot term where every post is new**, a per-result scraper is cheaper per post, and you should use one. SKYLARK owns the quiet-schedule loop, not one-shot bulk scraping.

### Why use it?

- **No app password required** for keyword/hashtag/mention monitoring (competing "monitor" actors make you create and paste a Bluesky app password just to search).
- **Durable, edit-safe state.** History is keyed by your **Task**, not by a hash of your query — so adding or changing a keyword never resets or orphans what you've already seen.
- **Arrays, not one string.** Watch many keywords, handles, and hashtags in a single task with a single merged feed, plus `excludeTerms` and language filters.
- **Engineered failover.** A 3-path data chain (see _Reliability_ below) instead of a single brittle endpoint.
- **Webhooks + MCP.** Fire-on-new webhooks for automation, and add this eligible paid Actor to an [Apify MCP](https://mcp.apify.com/) server for AI agents.

### How to use it (2-minute quickstart)

1. Click **Try for free**.
2. Add at least one **keyword**, **handle**, or **hashtag** (e.g. keyword `bluesky monitor`).
3. Click **Start**. The first run looks back 24 hours; every later run returns only what's new since the last one.
4. **Create a Task** (top-right **Create task**) and attach a **Schedule** (e.g. every hour) — this is what turns SKYLARK into an isolated recurring monitor. Ad-hoc runs share one persistent per-user state scope, so Tasks are strongly recommended.
5. (Optional) Paste a **Webhook URL** to get pushed the new posts the moment they appear.

#### Worked schedule example

Create a Task with this input, then add an hourly Schedule:

```json
{
    "keywords": ["british museum", "bayeux tapestry"],
    "handles": ["bsky.app"],
    "hashtags": ["photography"],
    "excludeTerms": ["giveaway"],
    "langs": ["en"],
    "onlyNew": true,
    "maxPostsPerRun": 100,
    "webhookUrl": "https://hooks.example.com/your-endpoint"
}
````

- **Console:** open the Task → **Actions ▸ Schedule Actor** → *Every hour* → Save.
- Each run charges `$0.002` to start plus `$0.003` per **new** post; a quiet hourly watch idles at roughly **$1.44/month** of start fees and a few cents of new-post charges.

### Integration recipes

The webhook fires **only when there are new posts**. Generic endpoints receive `{ "summary": {...}, "posts": [...up to 50 newest...] }`; Slack Incoming Webhooks receive formatted text.

- **n8n:** add a **Webhook** node (HTTP POST), copy its URL into `webhookUrl`. `{{$json.body.posts}}` is your array of new posts; `{{$json.body.summary.newPosts}}` is the count.
- **Make (Integromat):** create a scenario starting with **Webhooks ▸ Custom webhook**, paste the address into `webhookUrl`, then map `posts[]`.
- **Slack:** use a Slack **Incoming Webhook** URL directly as `webhookUrl` for a concise message with up to 10 post links, or route the generic payload through Make/n8n for custom formatting.
- **Generic / any endpoint / your own code:** POST body shape:

```json
{
    "summary": {
        "newPosts": 3,
        "matchedPosts": 3,
        "dataSourcePath": "P1",
        "saturatedP1": false,
        "capped": false,
        "droppedCount": 0,
        "chargeLimitReached": false,
        "failureCount": 0,
        "charged": { "actorStart": 0.002, "newPost": 0.009 },
        "finishedAt": "..."
    },
    "posts": [
        {
            "uri": "...",
            "url": "...",
            "author": { "handle": "..." },
            "text": "...",
            "matchedTerms": ["..."],
            "source": "keyword",
            "isNew": true
        }
    ]
}
```

You can also **skip the webhook** and pull results from the dataset via the Apify API after each scheduled run.

#### Use it from an AI agent (MCP)

Add SKYLARK to your paid Actor tools at **mcp.apify.com**. An agent can then call it like:

> "Watch Bluesky for mentions of `acme.bsky.social` and the keyword `product recall`, English only, and return only posts I haven't seen before."

which maps to `{ "handles": ["acme.bsky.social"], "keywords": ["product recall"], "langs": ["en"], "onlyNew": true }`. The only-new semantics are exactly what agent polling wants — no duplicate context on every poll.

### Input

Provide **at least one** of `keywords`, `handles`, or `hashtags`. All other fields are optional.

| Field            | Type     | Default | Description                                                                             |
| ---------------- | -------- | ------- | --------------------------------------------------------------------------------------- |
| `keywords`       | string\[] | –       | Full-text terms/phrases to watch.                                                       |
| `handles`        | string\[] | –       | Handles (± `@`). Each watched for its own posts **and** mentions of it.                 |
| `hashtags`       | string\[] | –       | Hashtags (± `#`).                                                                       |
| `excludeTerms`   | string\[] | `[]`    | Drop posts containing any of these (case-insensitive), before billing.                  |
| `langs`          | string\[] | `[]`    | ISO 639-1 codes; empty = all.                                                           |
| `onlyNew`        | boolean  | `true`  | Return/charge only posts new to this task. Off = plain search (still charged per post). |
| `maxPostsPerRun` | integer  | `100`   | Hard billing ceiling per run (1–800).                                                   |
| `webhookUrl`     | string   | –       | POSTed run summary + up to 50 newest posts, only when there are new posts.              |
| `resetState`     | boolean  | `false` | Wipe this task's seen-set + cursor and start fresh.                                     |

### Output

One dataset item per **new** matched post. Download as JSON, CSV, Excel, or HTML.

```json
{
    "uri": "at://did:plc:gxbshd27.../app.bsky.feed.post/3mqcp2p7zgk2p",
    "cid": "bafyreibuxa3ko...",
    "url": "https://bsky.app/profile/mikeinindiana.bsky.social/post/3mqcp2p7zgk2p",
    "author": {
        "did": "did:plc:gxbshd27...",
        "handle": "mikeinindiana.bsky.social",
        "displayName": "Mike Eads",
        "avatar": "https://cdn.bsky.app/img/avatar/plain/…"
    },
    "text": "The Bayeux Tapestry is being moved to the British Museum next year…",
    "createdAt": "2026-07-10T18:42:11.000Z",
    "indexedAt": "2026-07-10T18:42:13.512Z",
    "langs": ["en"],
    "likeCount": 14,
    "repostCount": 3,
    "replyCount": 2,
    "quoteCount": 0,
    "matchedTerms": ["british museum"],
    "source": "keyword",
    "isNew": true
}
```

| Field          | Meaning                                                                           |
| -------------- | --------------------------------------------------------------------------------- |
| `uri`          | Stable post identity used to dedupe.                                              |
| `cid`          | Content version identifier returned by Bluesky.                                   |
| `url`          | Human-clickable post link.                                                        |
| `matchedTerms` | Every input term that returned this post (emitted once even if multiple matched). |
| `source`       | `keyword` | `hashtag` | `handle` | `mention`.                                  |
| `isNew`        | Whether this post was new to the task this run.                                   |

### Pricing

Pay-per-event, billed by Apify:

| Event                 | Price                          |
| --------------------- | ------------------------------ |
| Actor start (per run) | **$0.002**                     |
| New post delivered    | **$0.003** ($3.00 / 1,000 new) |

Dataset writes and `new-post` charges use Apify's coupled PPE operation **after** dedup and filtering. Excluded, off-language, and already-seen posts are free. `maxPostsPerRun` and the buyer's maximum total charge cap the worst case of any single run.

### State semantics

- **"Only new"** means: not present in *this task's* active seen-set. Dedup is by post `uri`.
- **Task-keyed:** state is stored per Apify Task, so editing keywords keeps your history. Ad-hoc (un-scheduled) runs are keyed per user — create a Task for real monitoring.
- **Cursor:** tracked by the server-assigned `indexedAt` time (clamped to now), so future-dated spam posts can't break the monitor. First run looks back 24h.
- **`resetState: true`** wipes the seen-set and cursor once; turn it back off afterward.
- **TTL/cap:** seen post IDs older than 30 days are pruned automatically, and the oldest IDs are evicted above 50,000 per task. A post can be delivered again after expiry or eviction.

### Reliability & failover

SKYLARK uses a 3-path data chain and reports the active path as `dataSourcePath` in every run summary:

- **P1 — no-login public API (default).** Delivers up to **100 posts per term per poll**. If a poll comes back a full 100-post page that's *all new* (**`SATURATED_P1`**), that watch is auto-routed to the authenticated path to page out the backlog, and the run recommends a tighter schedule cadence. Choose a schedule frequent enough that fewer than 100 new posts arrive between runs.
- **P2 — authenticated fallback (best-effort).** Used for saturation/backlog and if P1 is unavailable. This path is served through an operator account, creates run-local sessions to avoid exposing derived credentials in caller storage, and is subject to Bluesky's service and rate-limit policies. There is **no capacity guarantee** above the P1 ceiling.
- **P3 — degraded replay mode.** If both search paths are unavailable, SKYLARK falls back to Jetstream with an up-to-five-minute replay cursor (keyword/hashtag only). Gaps longer than five minutes are not backfilled. In this degraded path, the DID is used as the author handle fallback and a CID may be unavailable.

If a run is capped (`capped: true`), `droppedCount` reports how many new posts exceeded `maxPostsPerRun` — raise the ceiling or tighten the schedule to catch them.

### How it compares

|                                                   | **SKYLARK**     | Enterprise scraper (stateless) | Other "monitor" clones   |
| ------------------------------------------------- | --------------- | ------------------------------ | ------------------------ |
| Charges only for **new** posts                    | ✅              | ❌ (re-charges duplicates)     | ✅                       |
| **No app password** for keyword/hashtag/mention   | ✅              | ✅                             | ❌ (require one)         |
| State survives query edits                        | ✅ (Task-keyed) | –                              | ❌ (keyed per query)     |
| Keywords **+** handles **+** hashtags in one task | ✅              | partial                        | ❌ (single query string) |
| `excludeTerms` + language filter                  | ✅              | partial                        | ❌                       |
| 3-path failover                                   | ✅              | ❌                             | ❌                       |
| Webhook on new + MCP-ready                        | ✅              | ❌                             | partial                  |
| Best per-post price on hot all-new terms          | ❌ (~$3/1k)     | ✅ (~$1.49/1k)                 | ~$1/1k                   |

We don't fight enterprise scrapers on one-shot bulk scraping — we own the recurring alert loop.

### FAQ

- **Do I need a Bluesky account or app password?** No, not for keyword, hashtag, or mention monitoring.
- **What happens on the first run?** It looks back 24 hours and delivers matches, then tracks forward from there.
- **What if more than 100 posts match in one poll?** P1 delivers the newest 100; the run flags `SATURATED_P1` and auto-pages via the authenticated path. Use a more frequent schedule for very active terms.
- **Is this legal?** It reads only public Bluesky posts via the public AT Protocol API and stores no private data. Respect Bluesky's Terms and applicable law for your use case.
- **Why is there a per-run start fee?** It's cost-recovery on runs with zero new posts (an hourly schedule idles at ~$1.44/month) and it prevents empty-schedule spam.
- **Found a bug / want a feature?** Open an issue on the Actor's **Issues** tab.

# Actor input Schema

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

Full-text terms or phrases to watch (e.g. "british museum", "my product"). At most 20 total targets across keywords, handles, and hashtags.

## `handles` (type: `array`):

Bluesky handles (with or without @). Each is watched two ways: their own new posts AND new posts that mention them.

## `hashtags` (type: `array`):

Hashtags to watch (with or without #).

## `excludeTerms` (type: `array`):

Drop any post whose text contains one of these (case-insensitive). Applied before charging — excluded posts are never billed.

## `langs` (type: `array`):

ISO 639-1 codes (e.g. en, de). Empty = all languages.

## `onlyNew` (type: `boolean`):

When on (default), return and charge for posts never delivered to this task before. Turn off to behave like a plain search (still charged per post).

## `maxPostsPerRun` (type: `integer`):

Hard cap on posts delivered/charged in a single run. P1 (no-login) delivers up to 100/term/poll; beyond that needs the authenticated path.

## `webhookUrl` (type: `string`):

Optional. POSTed a JSON payload (run summary + up to 50 newest posts) only when there are new posts. Works with n8n, Make, Slack, or any endpoint.

## `sort` (type: `string`):

Result ordering. Fixed to latest for monitoring semantics.

## `resetState` (type: `boolean`):

Wipe this task's seen-set and cursor and start fresh. Use once, then turn back off.

## Actor input object example

```json
{
  "keywords": [
    "bluesky"
  ],
  "excludeTerms": [],
  "langs": [],
  "onlyNew": true,
  "maxPostsPerRun": 10,
  "sort": "latest",
  "resetState": false
}
```

# Actor output Schema

## `posts` (type: `string`):

Default dataset containing the matched Bluesky post records emitted by this run.

## `runSummary` (type: `string`):

Run-level counts, source path, cap status, and charging summary.

# 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 = {
    "keywords": [
        "bluesky"
    ],
    "onlyNew": false,
    "maxPostsPerRun": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("uplifted_novice_vbl/bluesky-keyword-mention-alerts").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 = {
    "keywords": ["bluesky"],
    "onlyNew": False,
    "maxPostsPerRun": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("uplifted_novice_vbl/bluesky-keyword-mention-alerts").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "keywords": [
    "bluesky"
  ],
  "onlyNew": false,
  "maxPostsPerRun": 10
}' |
apify call uplifted_novice_vbl/bluesky-keyword-mention-alerts --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=uplifted_novice_vbl/bluesky-keyword-mention-alerts",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Bluesky Keyword & Mention Alerts — Only New Posts",
        "description": "Monitor Bluesky for keywords, mentions & hashtags on a schedule. Charges only for new posts. No login required.",
        "version": "0.1",
        "x-build-id": "ZsQVD9YBK3zXJtuhQ"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/uplifted_novice_vbl~bluesky-keyword-mention-alerts/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-uplifted_novice_vbl-bluesky-keyword-mention-alerts",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/uplifted_novice_vbl~bluesky-keyword-mention-alerts/runs": {
            "post": {
                "operationId": "runs-sync-uplifted_novice_vbl-bluesky-keyword-mention-alerts",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/uplifted_novice_vbl~bluesky-keyword-mention-alerts/run-sync": {
            "post": {
                "operationId": "run-sync-uplifted_novice_vbl-bluesky-keyword-mention-alerts",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "keywords": {
                        "title": "Keywords / phrases",
                        "maxItems": 20,
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Full-text terms or phrases to watch (e.g. \"british museum\", \"my product\"). At most 20 total targets across keywords, handles, and hashtags.",
                        "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 256
                        },
                        "default": [
                            "bluesky"
                        ]
                    },
                    "handles": {
                        "title": "Handles to monitor",
                        "maxItems": 20,
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Bluesky handles (with or without @). Each is watched two ways: their own new posts AND new posts that mention them.",
                        "items": {
                            "type": "string",
                            "minLength": 3,
                            "maxLength": 253,
                            "pattern": "^@?[A-Za-z0-9][A-Za-z0-9.-]*\\.[A-Za-z0-9.-]+$"
                        }
                    },
                    "hashtags": {
                        "title": "Hashtags",
                        "maxItems": 20,
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Hashtags to watch (with or without #).",
                        "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 64
                        }
                    },
                    "excludeTerms": {
                        "title": "Exclude terms",
                        "maxItems": 100,
                        "uniqueItems": true,
                        "type": "array",
                        "description": "Drop any post whose text contains one of these (case-insensitive). Applied before charging — excluded posts are never billed.",
                        "items": {
                            "type": "string",
                            "minLength": 1,
                            "maxLength": 256
                        },
                        "default": []
                    },
                    "langs": {
                        "title": "Languages",
                        "maxItems": 20,
                        "uniqueItems": true,
                        "type": "array",
                        "description": "ISO 639-1 codes (e.g. en, de). Empty = all languages.",
                        "items": {
                            "type": "string",
                            "minLength": 2,
                            "maxLength": 2,
                            "pattern": "^[A-Za-z]{2}$"
                        },
                        "default": []
                    },
                    "onlyNew": {
                        "title": "Only new posts",
                        "type": "boolean",
                        "description": "When on (default), return and charge for posts never delivered to this task before. Turn off to behave like a plain search (still charged per post).",
                        "default": true
                    },
                    "maxPostsPerRun": {
                        "title": "Max posts per run (billing ceiling)",
                        "minimum": 1,
                        "maximum": 800,
                        "type": "integer",
                        "description": "Hard cap on posts delivered/charged in a single run. P1 (no-login) delivers up to 100/term/poll; beyond that needs the authenticated path.",
                        "default": 100
                    },
                    "webhookUrl": {
                        "title": "Webhook URL",
                        "type": "string",
                        "description": "Optional. POSTed a JSON payload (run summary + up to 50 newest posts) only when there are new posts. Works with n8n, Make, Slack, or any endpoint."
                    },
                    "sort": {
                        "title": "Sort",
                        "enum": [
                            "latest"
                        ],
                        "type": "string",
                        "description": "Result ordering. Fixed to latest for monitoring semantics.",
                        "default": "latest"
                    },
                    "resetState": {
                        "title": "Reset state",
                        "type": "boolean",
                        "description": "Wipe this task's seen-set and cursor and start fresh. Use once, then turn back off.",
                        "default": false
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
