# Hacker News Scraper - Stories, Comments & AI Digest (`harvestlab/hacker-news-scraper`) Actor

Scrape HN top stories, search, Ask HN, Show HN, job posts and user profiles via Algolia & Firebase APIs. Zero anti-bot, no API key. AI topic digest (themes, trends, TLDR) via 5 LLM providers. x402-ready. $0.001/item.

- **URL**: https://apify.com/harvestlab/hacker-news-scraper.md
- **Developed by:** [Nick](https://apify.com/harvestlab) (community)
- **Categories:** Developer tools, AI, MCP servers
- **Stats:** 2 total users, 1 monthly users, 50.0% runs succeeded, NaN bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per usage

This Actor is paid per platform usage. The Actor is free to use, and you only pay for the Apify platform usage, which gets cheaper the higher subscription plan you have.

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

## 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

## Hacker News Scraper — Top/Show/Ask + Velocity + AI Digest
> 🧩 **Part of the harvestlab MCP suite** — 36 RAG-ready, AI-agent-payment-ready Apify actors covering ecommerce, social, travel, news, jobs, EU B2B, dev-tools, and government data. [See the full suite →](https://apify.com/harvestlab)


> **Track Hacker News stories at $0.001/story.** Fetches top, new, best, show, ask, and jobs feeds via the official Hacker News Firebase API — no API key, no rate limits, no anti-bot. Within-run velocity tracking + cross-run score-delta snapshot flags trending stories. AI executive digest (themes, trends, TLDR) via 5-provider LLM router. Webhook alerts on trending detection. **pay-per-result · no-cookies · no-rental-tax.**

Built for: developer-relations teams, VC scouts, technical founders, content writers, AI agent builders. Pairs with `harvestlab/github-trending-scraper` for a complete "developer intelligence" pipeline.

---

### What this does

The Hacker News front page is the single highest-signal feed for early-stage tech, AI launches, and indie-dev culture. But polling it manually is tedious, and existing scrapers either hit Algolia's downstream API (lossy) or scrape the HTML (fragile). This actor uses the **official Hacker News Firebase API** — the same backend the YC site uses — which means:

- **Zero rate limits** (Firebase API is unmetered for read traffic)
- **No API key** required
- **No anti-bot** to bypass
- **Full fidelity**: scores, descendants, kids, raw item structure
- **Six feeds** in one input: `top`, `new`, `best`, `show`, `ask`, `jobs`

Every story is normalized to a portfolio-standard shape (`id`, `title`, `url`, `score`, `by`, `time`, `descendants`, `kids`, `text`, `trending`). Optional comment fan-out fetches the top-N comments per story. Optional AI digest summarizes the top 10 stories using your choice of 5 LLM providers (OpenRouter, Anthropic, Google AI, OpenAI, or self-hosted Ollama).

### Why this beats the alternatives

| Approach | Cost | Limits | AI digest | Trending detection |
|---|---|---|---|---|
| **harvestlab/hacker-news-scraper** | **$0.001/story** | None | Yes (5 LLMs) | Yes (within-run + cross-run score-delta) |
| HN Algolia API (free) | $0 | 10k/hr soft cap | No | No |
| Algolia HN third-party scrapers | $5-29/mo | Plan-tiered | No | No |
| Custom Firebase fetch | DIY | None | DIY | DIY |
| RSS aggregators | $5-15/mo | Limited fields | No | No |

**The wedge**: dual-layer velocity tracking in a pay-per-event scraper. Within-run velocity (`trending`) fires at ≥30 pts/hr after the first 30 min; cross-run KV-store snapshot (`trending_cross_run`) fires when a story gains ≥20 points since the last run — together they catch both fast-rising newcomers and stories re-entering the front page. Algolia gives you a search index but no velocity signal — you can't tell if a story is *accelerating* in real time. Add `enableAiAnalysis=true` for an AI executive digest (themes, trends, TLDR) via your choice of 5 LLM providers.

### Use cases (8 personas)

1. **Developer-relations teams** — daily watch on `Show HN` for early adopters of competing tools.
2. **VC scouts** — `Show HN` + AI digest = pre-seed deal flow before the post hits 100 points.
3. **Indie founders** — track competitor launches and AI announcements.
4. **Content writers / Substackers** — auto-curated story shortlist for "this week in AI" newsletters.
5. **AI agent builders** — feed top-10 + comments into a research agent for trend analysis.
6. **Recruiters** — `jobstories` feed + KvK-style enrichment finds startups actively hiring.
7. **Product managers** — competitive intel on AI tools shipping in your category.
8. **Researchers / academics** — longitudinal study of HN trending dynamics with reproducible velocity flag.

### Inputs

| Field | Type | Default | Notes |
|---|---|---|---|
| `feed` | enum | `top` | One of: top, new, best, show, ask, jobs |
| `maxStories` | integer | 30 | Stories to fetch (1-500) |
| `includeComments` | boolean | false | Adds ~$0.0005/comment |
| `maxCommentsPerStory` | integer | 20 | Cap top-level comments (cost control) |
| `minPoints` | integer | 0 | Skip stories below this score |
| `trackVelocity` | boolean | true | Flag stories with >30 pts/hr (after 30min) |
| `alertWebhookUrl` | string | — | Slack/Zapier/n8n/Discord webhook URL |
| `enableAiAnalysis` | boolean | false | AI executive digest (themes, trends, TLDR) |
| `llmProvider` | enum | `openrouter` | AI provider: openrouter / anthropic / google / openai / ollama |
| `llmModel` | string | — | Model override (uses provider default if blank) |
| `openrouterApiKey` / `anthropicApiKey` / `googleApiKey` / `openaiApiKey` / `ollamaBaseUrl` | string | — | API key for chosen provider |

### Outputs

One dataset item per story:
```json
{
  "id": 39481275,
  "type": "story",
  "title": "Show HN: Hacker News Scraper at $0.001/story",
  "url": "https://apify.com/harvestlab/hacker-news-scraper",
  "score": 142,
  "by": "harvestlab",
  "time": 1714162800,
  "descendants": 38,
  "kids": [39481276, 39481289, ...],
  "trending": true,
  "score_delta": 42,
  "trending_cross_run": true,
  "comments": [
    {"id": 39481276, "by": "alice", "time": 1714163700, "text": "Nice work!"}
  ]
}
````

When `enableAiAnalysis=true`, an additional dataset item with `report_type: "ai_digest"` is appended containing: `top_themes`, `notable_stories`, `tech_trends`, `community_mood`, `standout_discussions`, and a `tldr` field suitable for newsletter intros. Charged only when the LLM returns parseable JSON.

### Pricing

| Event | Price | When |
|---|---|---|
| `story-scraped` | $0.001 | Per story successfully fetched |
| `comment-scraped` | $0.0005 | Per comment when `includeComments=true` |
| `alert-dispatched` | $0.002 | Per webhook POST on trending detection |
| `ai-analysis-completed` | $0.05 | Per AI digest run |

**Typical run cost**: 30 top stories, no comments, no AI = $0.03. With comments: $0.33. With AI digest: $0.08. Daily watch (top + AI digest) ≈ $1/month.

**vs. commercial alternatives**: NewsAPI charges $449+/mo for commercial use, and custom RSS monitoring solutions require ongoing infrastructure. This actor uses pay-per-event with no subscription: $0.001/story and zero monthly fees.

### Webhook payload schema

When a trending story fires, the webhook receives:

```json
{
  "type": "trending_story",
  "story": {
    "id": 39481275,
    "title": "...",
    "url": "https://...",
    "score": 142,
    "by": "alice"
  }
}
```

Compatible with Slack incoming webhooks (formats `text` automatically when wrapped in a Slack-shaped payload via Zapier or n8n), Discord, and any HTTP endpoint accepting JSON. Failed dispatches are not charged.

### Scheduling for daily watch

1. Set `feed: "top"` + `enableAiAnalysis: true` + `alertWebhookUrl: "<your-slack-incoming-webhook>"`.
2. Schedule the actor to run hourly via Apify Scheduler.
3. Two velocity signals work together: `trending` (within-run ≥30 pts/hr) and `trending_cross_run` (score gained ≥20 points vs. the previous run's KV snapshot). Both trigger webhook alerts.
4. Estimated monthly cost at hourly cadence: **~$1.50/month** for top 30 + AI digest + Slack webhook.

This replaces a $5/month RSS aggregator with one that surfaces trending stories instead of all stories — way less noise.

### Pair with other harvestlab actors

- **`harvestlab/github-trending-scraper`** — pair HN's "what's launched today" with GitHub's "what's accelerating in stars" for a complete developer-intelligence pipeline.
- **`harvestlab/contact-extractor`** — feed each new HN `Show HN` URL into the contact extractor to build a `Show HN founder` lead list.
- **`harvestlab/news-monitor`** — combine HN front page with Google News for cross-source coverage of AI launches.
- **`harvestlab/google-search-scraper`** — when an HN story hits #1, scrape the SERP for backlinks and downstream coverage.

### Use with AI agents

hacker-news-scraper outputs HN stories + top-level comments + dual velocity signals (`trending` within-run + `trending_cross_run` score-delta across runs) as structured JSON from the **official Hacker News Firebase API** ($0.001/story, no key, no rate limits). Enable AI digest for a structured briefing: top themes, tech trends, notable stories, community mood, and a newsletter-ready TLDR. RAG-ready for developer-research agents and Show-HN deal-flow scouts.

**LangChain — `mine_hacker_news` tool:**

```python
from langchain.tools import Tool
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

def mine_hacker_news(params: dict) -> list:
    run = client.actor("harvestlab/hacker-news-scraper").call(run_input={
        "feed": params.get("feed", "top"),
        "maxStories": params.get("maxStories", 30),
        "includeComments": params.get("fetchComments", False),
        "trackVelocity": True,
    })
    return list(client.dataset(run["defaultDatasetId"]).iterate_items())

mine_hn_tool = Tool(
    name="mine_hacker_news",
    description="Fetch HN stories (top/new/best/show/ask/jobs) with within-run velocity 'trending' flags and optional comments via the official Firebase API.",
    func=mine_hacker_news,
)
## agent.invoke({"input": "What's trending on Show HN right now?"})
```

**LangGraph — node in a developer-research graph:**

```python
from langgraph.graph import StateGraph
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

def hn_node(state: dict) -> dict:
    run = client.actor("harvestlab/hacker-news-scraper").call(run_input={
        "feed": "top",
        "maxStories": 50,
        "includeComments": True,
        "maxCommentsPerStory": 20,
        "trackVelocity": True,
    })
    items = list(client.dataset(run["defaultDatasetId"]).iterate_items())
    keywords = [k.lower() for k in state.get("topic_keywords", [])]
    matched = [s for s in items if any(k in (s.get("title") or "").lower() for k in keywords)]
    return {**state, "hn_threads": matched, "trending": [s for s in matched if s.get("trending")]}

graph = StateGraph(dict)
graph.add_node("hn", hn_node)
## wire into downstream sentiment-on-comments / summarizer / Slack-digest nodes
```

See Apify's [`actor-templates/js-langchain`](https://github.com/apify/actor-templates/tree/master/templates/js-langchain) and [`js-langgraph-agent`](https://github.com/apify/actor-templates/tree/master/templates/js-langgraph-agent) for full reference setups.

### Compliance & legal

The Hacker News Firebase API is **public, documented, and free** (https://github.com/HackerNews/API). Y Combinator publishes it explicitly for third-party use. No ToS violation, no scraping of authenticated routes, no fingerprinting.

That said, **users are responsible for**:

- Respecting rate norms (don't fan-out 100 concurrent fetches per second; use `maxStories ≤ 500` and reasonable cadence)
- GDPR / CCPA compliance when storing usernames + comment text (HN comments contain PII per author choice)
- Attribution when redistributing data (link back to the HN item via `https://news.ycombinator.com/item?id=<id>`)

### Roadmap

- **v0.3**: Algolia downstream search fallback for historical queries (>14 days old).
- **v0.3**: User-profile fetching (`/user/<by>.json`) for author rep + karma.

### Contact

Built by [harvestlab](https://apify.com/harvestlab) — 25 monetized Apify Actors covering EU/US e-commerce, B2B intelligence, jobs/salary, government procurement, and developer tools. Bug reports + feature requests via the Apify Store issue tracker on this actor's listing page.

# Actor input Schema

## `feed` (type: `string`):

Which HN feed to scrape.

## `maxStories` (type: `integer`):

Number of stories to fetch from the chosen feed.

## `includeComments` (type: `boolean`):

Fetch top-level comments for each story (adds ~$0.0005/comment).

## `maxCommentsPerStory` (type: `integer`):

Cap top-level comments per story (cost control).

## `minPoints` (type: `integer`):

Filter stories below this score (set 0 to skip filter).

## `trackVelocity` (type: `boolean`):

Cross-run velocity flag: stories whose points/hour exceed a threshold are tagged 'trending'.

## `alertWebhookUrl` (type: `string`):

Slack/Zapier/n8n/Discord webhook. Fires on trending stories.

## `enableAiAnalysis` (type: `boolean`):

Generate an AI executive digest of top stories (themes, trends, TLDR). Adds ~$0.05 per run. Requires an API key for the chosen provider.

## `llmProvider` (type: `string`):

LLM provider for AI digest.

## `llmModel` (type: `string`):

Specific model name (uses provider default if blank). Examples: anthropic/claude-3-5-haiku-20241022, gemini-2.5-pro, gpt-4o.

## `openrouterApiKey` (type: `string`):

OpenRouter API key missing — set OPENROUTER\_API\_KEY env var OR openrouterApiKey input. Get one at https://openrouter.ai/keys.

## `anthropicApiKey` (type: `string`):

Anthropic API key missing — set ANTHROPIC\_API\_KEY env var OR anthropicApiKey input. Get one at https://console.anthropic.com/settings/keys.

## `googleApiKey` (type: `string`):

Google AI API key missing — set GOOGLE\_API\_KEY env var OR googleApiKey input. Get one at https://aistudio.google.com/app/apikey.

## `openaiApiKey` (type: `string`):

OpenAI API key missing — set OPENAI\_API\_KEY env var OR openaiApiKey input. Get one at https://platform.openai.com/api-keys.

## `ollamaBaseUrl` (type: `string`):

Self-hosted Ollama URL. Default http://localhost:11434. Install at https://ollama.com/download.

## Actor input object example

```json
{
  "feed": "top",
  "maxStories": 30,
  "includeComments": false,
  "maxCommentsPerStory": 20,
  "minPoints": 0,
  "trackVelocity": true,
  "enableAiAnalysis": false,
  "llmProvider": "openrouter",
  "ollamaBaseUrl": "http://localhost:11434"
}
```

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("harvestlab/hacker-news-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("harvestlab/hacker-news-scraper").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 '{}' |
apify call harvestlab/hacker-news-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=harvestlab/hacker-news-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "Hacker News Scraper - Stories, Comments & AI Digest",
        "description": "Scrape HN top stories, search, Ask HN, Show HN, job posts and user profiles via Algolia & Firebase APIs. Zero anti-bot, no API key. AI topic digest (themes, trends, TLDR) via 5 LLM providers. x402-ready. $0.001/item.",
        "version": "0.1",
        "x-build-id": "6EPQ3arqCKSGAt5NP"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/harvestlab~hacker-news-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-harvestlab-hacker-news-scraper",
                "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/harvestlab~hacker-news-scraper/runs": {
            "post": {
                "operationId": "runs-sync-harvestlab-hacker-news-scraper",
                "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/harvestlab~hacker-news-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-harvestlab-hacker-news-scraper",
                "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": {
                    "feed": {
                        "title": "Feed",
                        "enum": [
                            "top",
                            "new",
                            "best",
                            "show",
                            "ask",
                            "jobs"
                        ],
                        "type": "string",
                        "description": "Which HN feed to scrape.",
                        "default": "top"
                    },
                    "maxStories": {
                        "title": "Max stories",
                        "minimum": 1,
                        "maximum": 500,
                        "type": "integer",
                        "description": "Number of stories to fetch from the chosen feed.",
                        "default": 30
                    },
                    "includeComments": {
                        "title": "Include comments",
                        "type": "boolean",
                        "description": "Fetch top-level comments for each story (adds ~$0.0005/comment).",
                        "default": false
                    },
                    "maxCommentsPerStory": {
                        "title": "Max comments per story",
                        "minimum": 0,
                        "maximum": 200,
                        "type": "integer",
                        "description": "Cap top-level comments per story (cost control).",
                        "default": 20
                    },
                    "minPoints": {
                        "title": "Min points",
                        "minimum": 0,
                        "type": "integer",
                        "description": "Filter stories below this score (set 0 to skip filter).",
                        "default": 0
                    },
                    "trackVelocity": {
                        "title": "Track velocity",
                        "type": "boolean",
                        "description": "Cross-run velocity flag: stories whose points/hour exceed a threshold are tagged 'trending'.",
                        "default": true
                    },
                    "alertWebhookUrl": {
                        "title": "Alert webhook URL",
                        "type": "string",
                        "description": "Slack/Zapier/n8n/Discord webhook. Fires on trending stories."
                    },
                    "enableAiAnalysis": {
                        "title": "Enable AI analysis",
                        "type": "boolean",
                        "description": "Generate an AI executive digest of top stories (themes, trends, TLDR). Adds ~$0.05 per run. Requires an API key for the chosen provider.",
                        "default": false
                    },
                    "llmProvider": {
                        "title": "AI provider",
                        "enum": [
                            "openrouter",
                            "anthropic",
                            "google",
                            "openai",
                            "ollama"
                        ],
                        "type": "string",
                        "description": "LLM provider for AI digest.",
                        "default": "openrouter"
                    },
                    "llmModel": {
                        "title": "AI model (optional)",
                        "type": "string",
                        "description": "Specific model name (uses provider default if blank). Examples: anthropic/claude-3-5-haiku-20241022, gemini-2.5-pro, gpt-4o."
                    },
                    "openrouterApiKey": {
                        "title": "OpenRouter API key",
                        "type": "string",
                        "description": "OpenRouter API key missing — set OPENROUTER_API_KEY env var OR openrouterApiKey input. Get one at https://openrouter.ai/keys."
                    },
                    "anthropicApiKey": {
                        "title": "Anthropic API key",
                        "type": "string",
                        "description": "Anthropic API key missing — set ANTHROPIC_API_KEY env var OR anthropicApiKey input. Get one at https://console.anthropic.com/settings/keys."
                    },
                    "googleApiKey": {
                        "title": "Google AI API key",
                        "type": "string",
                        "description": "Google AI API key missing — set GOOGLE_API_KEY env var OR googleApiKey input. Get one at https://aistudio.google.com/app/apikey."
                    },
                    "openaiApiKey": {
                        "title": "OpenAI API key",
                        "type": "string",
                        "description": "OpenAI API key missing — set OPENAI_API_KEY env var OR openaiApiKey input. Get one at https://platform.openai.com/api-keys."
                    },
                    "ollamaBaseUrl": {
                        "title": "Ollama base URL",
                        "type": "string",
                        "description": "Self-hosted Ollama URL. Default http://localhost:11434. Install at https://ollama.com/download.",
                        "default": "http://localhost:11434"
                    }
                }
            },
            "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
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
