# Task Runner — Run Apify Actors from a Notion or Airtable Board (`deadwood_data_solutions/connector-task-runner`) Actor

Turn a Notion database, Airtable base or Supabase table into a job queue. Reads pending rows, runs an Apify Actor for each, and writes status, run ID, dataset ID and result count back to the row.

- **URL**: https://apify.com/deadwood\_data\_solutions/connector-task-runner.md
- **Developed by:** [K O](https://apify.com/deadwood_data_solutions) (community)
- **Categories:** Automation, Developer tools, Integrations
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 job row completeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

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

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

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

# README

## Task Runner — Run Apify Actors from a Notion or Airtable Board

Turn the board your team already works in into a job queue.

Add a row: a search term, a URL, a city. Set its status to **Pending**. This Actor picks it up, runs the Apify Actor you chose, and writes the outcome back to that same row — status, run ID, dataset ID, how many results came back.

Nobody has to open Apify Console to kick off a scrape, and nobody has to go looking for where the results went.

### Why a board instead of a schedule

A schedule runs the same thing forever. A board is a **queue you can edit**: a salesperson adds three cities on Monday, a researcher drops in ten competitor URLs, and each row is picked up on the next pass and reports back where it landed. The work list and the run log end up in one place, visible to people who don't have Apify accounts.

### How it works

1. Connect your app under **Integrations** in Apify Console.
2. Run in **`list-tools`** mode to learn the connector's tool names.
3. Run in **`run`** mode — ideally on a schedule, so the board is drained continuously.

```json
{
  "board": "<your connector>",
  "mode": "run",
  "readTool": "query_database",
  "readToolArgs": { "database_id": "abc123" },
  "updateTool": "update_page",
  "idField": "id",
  "idArgName": "page_id",
  "statusField": "Status",
  "pendingValue": "Pending",
  "defaultActorId": "apify/google-maps-scraper",
  "inputMap": { "searchStringsArray": "Query", "maxCrawledPlaces": "Limit" },
  "datasetIdField": "Dataset",
  "itemCountField": "Results"
}
```

A board row like `Query: "roofers in Tucson" · Limit: 50 · Status: Pending` becomes a Google Maps run, and comes back as `Status: Done · Results: 50 · Dataset: xyz789`.

### Safety properties

- **Rows are claimed before they run.** Status flips to `Running` first, so a second pass — or a schedule that overlaps itself — never runs the same row twice.
- **Unusable rows are skipped, not failed.** Missing input, missing ID, or no Actor to run means the row is left alone and counted. A queue that marks rows failed for being blank is a queue nobody trusts.
- **A write-back failure never kills the queue.** If the board rejects an update, it's logged and the remaining tasks still run.
- **`dryRun`** shows exactly which rows would run, with the exact input each would get, without starting anything.
- **`maxTasks`** caps how many child runs one pass can start. Each task consumes your Apify compute — keep it modest until you trust the mapping.

### Input

| Field | Required | Description |
|---|---|---|
| `board` | yes | MCP connector holding the task list |
| `mode` | | `run` (default) or `list-tools` |
| `readTool` / `readToolArgs` | for run | Tool that reads the board |
| `updateTool` / `updateToolExtraArgs` | for run | Tool that patches a row |
| `idField` / `idArgName` | | Where the row ID lives, and what the update tool calls it |
| `statusField` | | Field holding row state. Default `Status` |
| `pendingValue` | | Value meaning "ready". Default `Pending`, matched case-insensitively |
| `runningValue` / `doneValue` / `failedValue` | | Values written as the task progresses |
| `defaultActorId` | one of | Actor to run for every row |
| `actorField` | one of | Row field letting each row pick its own Actor |
| `inputMap` | | Child Actor input key → row field path |
| `staticInput` | | Constant input for every task; row values win |
| `runIdField`, `datasetIdField`, `itemCountField`, `errorField` | | Optional write-back fields |
| `maxTasks` | | Child runs per pass. Default 25 |
| `memoryMbytes` / `timeoutSecs` | | Resources for each child run |
| `dryRun` | | Plan only, run nothing |

### Output

One row per task:

```json
{ "rowId": "page_123", "actorId": "apify/google-maps-scraper", "status": "Done",
  "itemCount": 50, "datasetId": "xyz789", "runId": "run_456", "error": null }
```

### Cost

**$5 per 1,000 tasks completed** by this Actor.

Note that each task also starts a **child Actor run**, which is billed separately under whatever that Actor charges plus your own Apify compute. This Actor orchestrates; it doesn't absorb the cost of what it orchestrates. Use `dryRun` and `maxTasks` to see the shape of a pass before letting it loose.

### FAQ

**Does this see my Notion token?** No. Apify's MCP proxy holds credentials and attaches them to outbound calls.

**Can different rows run different Actors?** Yes — set `actorField` to a column naming the Actor.

**What if a child Actor fails?** The row gets your `failedValue` and, if configured, the error text. The rest of the queue continues.

**What if two passes overlap?** The first claims rows by flipping them to `Running`, so the second sees nothing pending.

**Can an AI agent call this?** Yes — it's exposed through the Apify MCP server.

# Actor input Schema

## `board` (type: `string`):

The MCP connector holding your task list (Notion database, Airtable base, Supabase table...). Connect the app under Integrations in Apify Console first. Credentials stay in Apify's proxy — this Actor never sees your tokens.

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

Use 'list-tools' first to discover the connector's tool names, then 'run'.

## `readTool` (type: `string`):

MCP tool returning your task rows, e.g. 'query\_database' for Notion, 'select' for Supabase.

## `readToolArgs` (type: `object`):

JSON arguments the read tool needs, e.g. {"database\_id": "abc123"}.

## `updateTool` (type: `string`):

MCP tool that patches a row, e.g. 'update\_page' for Notion. Used to write status back.

## `updateToolExtraArgs` (type: `object`):

Fixed arguments merged into every update call.

## `idField` (type: `string`):

Where the row's identifier lives in the read result, e.g. 'id' or 'page\_id'.

## `idArgName` (type: `string`):

What the update tool calls its identifier argument, e.g. 'page\_id' for Notion.

## `statusField` (type: `string`):

Field on each row holding its state.

## `pendingValue` (type: `string`):

Only rows whose status equals this are picked up. Matching ignores case.

## `runningValue` (type: `string`):

Written before the child Actor starts, so the same row is never picked up twice.

## `doneValue` (type: `string`):

Written to the row after the child Actor run succeeds.

## `failedValue` (type: `string`):

Written to the row if the child Actor run errors or finishes unsuccessfully.

## `defaultActorId` (type: `string`):

Actor ID or name to run per row, e.g. 'apify/google-maps-scraper'. Optional if every row names its own Actor.

## `actorField` (type: `string`):

Optional. Lets each row choose its own Actor. Overrides the default above.

## `inputMap` (type: `object`):

Maps the child Actor's input keys to row field paths, e.g. {"searchStringsArray": "Query", "maxCrawledPlaces": "Limit"}.

## `staticInput` (type: `object`):

Constant input merged into every child run, e.g. {"language": "en"}. Row values win on conflict.

## `runIdField` (type: `string`):

Optional write-back field.

## `datasetIdField` (type: `string`):

Optional write-back field.

## `itemCountField` (type: `string`):

Optional write-back field.

## `errorField` (type: `string`):

Optional write-back field, truncated to 500 characters.

## `maxTasks` (type: `integer`):

Each task starts a child Actor run that consumes your Apify compute. Keep this modest until you trust the mapping.

## `memoryMbytes` (type: `integer`):

Memory allocated to each child Actor run. Higher memory costs more per second but can finish faster.

## `timeoutSecs` (type: `integer`):

How long a single child Actor run may take before it is stopped and the row is marked failed.

## `dryRun` (type: `boolean`):

Report which rows would run and with what input, without starting any child Actor or touching the board.

## Actor input object example

```json
{
  "mode": "run",
  "readToolArgs": {},
  "updateToolExtraArgs": {},
  "idField": "id",
  "idArgName": "id",
  "statusField": "Status",
  "pendingValue": "Pending",
  "runningValue": "Running",
  "doneValue": "Done",
  "failedValue": "Failed",
  "inputMap": {},
  "staticInput": {},
  "maxTasks": 25,
  "memoryMbytes": 1024,
  "timeoutSecs": 300,
  "dryRun": false
}
```

# 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 = {
    "readToolArgs": {},
    "updateToolExtraArgs": {},
    "inputMap": {},
    "staticInput": {}
};

// Run the Actor and wait for it to finish
const run = await client.actor("deadwood_data_solutions/connector-task-runner").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 = {
    "readToolArgs": {},
    "updateToolExtraArgs": {},
    "inputMap": {},
    "staticInput": {},
}

# Run the Actor and wait for it to finish
run = client.actor("deadwood_data_solutions/connector-task-runner").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 '{
  "readToolArgs": {},
  "updateToolExtraArgs": {},
  "inputMap": {},
  "staticInput": {}
}' |
apify call deadwood_data_solutions/connector-task-runner --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,deadwood_data_solutions/connector-task-runner"
        }
    }
}

```

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/lDb6SegfPezJXPOzr/builds/WflxZpNnh062WyFMl/openapi.json
