Rowbound
Open-source CLI and MCP server for Google Sheets enrichment — waterfalls, conditions, HTTP API integrations
Install / Use
/learn @eliasstravik/RowboundQuality Score
Category
Development & EngineeringSupported Platforms
README
Demo

Install
Prerequisites
- Node.js 22+ —
node --versionmust be >= 22.0.0 - gws CLI — Google Workspace CLI for Sheets access
npm install -g @googleworkspace/cli
gws auth setup # first time: creates Cloud project, enables APIs, logs in
gws auth login # subsequent logins
Quick install
npm install -g github:eliasstravik/rowbound
Build from source
git clone https://github.com/eliasstravik/rowbound.git
cd rowbound
npm install
npm run dev -- <command>
Quick Start
# 1. Initialize a sheet
rowbound init <spreadsheet-id>
# 2. Add an action
rowbound config add-action <spreadsheet-id> --json '{
"id": "enrich_company",
"type": "http",
"target": "company_info",
"method": "GET",
"url": "https://api.example.com/company?domain={{row.domain}}",
"headers": { "Authorization": "Bearer {{env.API_KEY}}" },
"extract": "$.name"
}'
# 3. Store API keys and run
rowbound env set API_KEY=your_key_here
rowbound run <spreadsheet-id>
rowbound run <spreadsheet-id> --dry-run # preview first
Column names are automatically resolved to stable IDs when you run rowbound sync.
Features
Sources — Create rows from external data
- HTTP sources — fetch from any API, extract array from response, map columns via JSONPath
- Exec sources — run shell commands, parse JSON output into rows
- Script sources — run named scripts to generate rows from their output
- Webhook sources — accept inbound POST payloads, create rows in real-time
- Deduplication — skip or update existing rows based on a match column
- Scheduling — run sources manually, hourly, daily, or weekly
Actions — Enrich existing rows
- HTTP actions — call any REST API with templated URLs, headers, and bodies; extract values with JSONPath;
ifEmptyfallback when extract returns nothing - Waterfall actions — try multiple providers in order until one returns a result (e.g., Clearbit → Apollo → Hunter)
- Formula actions — compute derived values with JavaScript expressions using
{{Column Name}}references - AI actions — run headless Claude or Codex per row with configurable model, max turns, tools, and
baremode for faster startup - Exec actions — run shell commands and capture stdout
- Script actions — run reusable named scripts stored in config; supports bash, python3, and node runtimes
- Lookup actions — pull data from other tabs (boolean, count, or full row JSON)
- Write actions — push data to other tabs with column mapping; supports append, upsert, and array expansion via
expandPath - Per-action environment variables — inject env vars per action (e.g.,
PLAYWRIGHT_HEADLESS=true)
Pipeline
- Conditional execution — skip actions per-row with
whenexpressions - Smart skip — automatically skips rows where the target cell already has a value
- Watch mode — poll sheets on an interval or trigger runs via webhook
- Column tracking — stable column IDs that survive header renames and reordering
--columnsflag — target specific columns by letter (e.g.,--columns A-C,E,AP)--rowsflag — flexible row specs (e.g.,--rows 2-5,8,10-12)- Rate limiting — configurable seconds between requests (default: 1 per second)
- Timeouts in seconds — all user-facing timeouts in seconds, not milliseconds
- Retry with backoff — exponential, linear, or fixed backoff on failures
- Structured error handling — per-action
onErrorconfig maps status codes to actions (skip, write fallback) - MCP server — expose all operations as Model Context Protocol tools for Claude Desktop and other AI assistants
- Run history — track pipeline executions with per-action summaries, durations, and error logs
- Dry run — preview what would change without writing to the sheet
- Per-tab stop/start — enable or disable processing per tab; stops mid-run if toggled during execution
- Per-tab settings — override concurrency, rate limit, retries, and backoff per tab
- BYOK — bring your own API keys, pay only for the APIs you use
CLI Commands
| Command | Description |
|---------|-------------|
| rowbound init <sheetId> | Initialize a sheet with a default pipeline config |
| rowbound run <sheetId> | Run the enrichment pipeline (--dry-run, --rows, --columns) |
| rowbound status <sheetId> | Show pipeline status and enrichment rates |
| rowbound watch <sheetId> | Watch for changes and run continuously (--interval, --port) |
| rowbound sync <sheetId> | Reconcile columns, validate config, fix issues |
| rowbound config show <sheetId> | Display the pipeline config as JSON |
| rowbound config add-action <sheetId> | Add an action to the pipeline |
| rowbound config remove-action <sheetId> | Remove an action by ID |
| rowbound config update-action <sheetId> | Update an action (merge partial JSON) |
| rowbound config list-actions <sheetId> | List configured actions (--json) |
| rowbound config add-source <sheetId> | Add a source to the pipeline |
| rowbound config remove-source <sheetId> | Remove a source by ID |
| rowbound config update-source <sheetId> | Update a source (merge partial JSON) |
| rowbound config set <sheetId> | Update pipeline settings (--enabled, --disabled, --concurrency, --rate-limit, etc.) |
| rowbound config add-script <sheetId> | Add a script to the pipeline config |
| rowbound config remove-script <sheetId> | Remove a script by name |
| rowbound config update-script <sheetId> | Update a script (merge partial JSON) |
| rowbound config validate <sheetId> | Validate the pipeline config |
| rowbound runs [runId] | List recent runs or view a specific run |
| rowbound runs clear | Delete all run history |
| rowbound source run <sheetId> | Run a source to create rows (--source, --dry-run) |
| rowbound source list <sheetId> | List configured sources |
| rowbound env set <KEY=value> | Store an API key globally |
| rowbound env remove <KEY> | Remove a stored key |
| rowbound env list | List stored keys (values masked) |
| rowbound mcp | Start the MCP server (stdio) |
MCP Server
Rowbound exposes all pipeline operations as MCP tools. Add this to your Claude Desktop config:
{
"mcpServers": {
"rowbound": {
"command": "rowbound",
"args": ["mcp"]
}
}
}
| Tool | Description |
|------|-------------|
| init_pipeline | Initialize a sheet with a default pipeline config |
| run_pipeline | Run the enrichment pipeline |
| add_action / remove_action / update_action | Manage pipeline actions |
| add_source / remove_source / update_source | Manage data sources |
| run_source | Execute a source to create rows |
| update_settings | Update pipeline settings (concurrency, rate limit, retry) |
| sync_columns | Sync the column registry with the current sheet state |
| get_config / validate_config | Read or validate the pipeline config |
| get_status | Return pipeline status with enrichment rates |
| dry_run | Run in dry mode (no writes) |
| start_watch / stop_watch | Manage watch mode |
| preview_rows | Read and display rows from the sheet |
| list_runs / get_run | View pipeline run history |
Source Types
Sources create rows from external data. They run before actions in the pipeline — new rows are created first, then actions enrich them on the next run.
http source
Fetch from an API and create rows from the response.
{
"id": "search_companies",
"type": "http",
"method": "POST",
"url": "https://api.blitz-api.ai/v2/search/company",
"headers": { "x-api-key": "{{env.BLITZ_API_KEY}}" },
"body": { "industry": "restaurants", "country_code": ["SE"] },
"extract": "$",
"extractPath": "$.results",
"columns": { "Title": "$.company_name", "Website": "$.website_url", "LinkedIn": "$.linkedin_url" },
"dedup": "Website",
"schedule": "daily"
}
exec source
Run a shell command and parse JSON output into rows.
{
"id": "import_leads",
"type": "exec",
"command": "curl -s https://api.example.com/leads",
"extract": "$.data",
"columns": { "Name": "$.name", "Email": "$.email" },
"dedup": "Email",
"updateExisting": true
}
webhook source
Accept inbound POST payloads and create rows. Used with rowbound watch.
{
"id": "form_submissions",
"type": "webhook",
"columns": { "Name": "$.name", "Email": "$.email", "Company": "$.company" },
"dedup": "Email"
}
Source options
| Field | Description |
|-------|-------------|
| columns | Maps sheet column headers to JSONPath per item: { "Name": "$.name" }. Use $.nested.field for nested data, or literal strings for static values. |
| extract / extractPath | JSONPath to locate the array in the response. extractPath drills into a nested object first (e.g., $.results extracts from {"results": [...]}). |
| dedup | Column header to deduplicate on. Existing rows with the same value are skipped. |
| updateExisting | When true and dedup is set, update matched rows instead of skipping (default: false). |
| schedule | "manual" (default), "hourly", "daily", or "weekly". Watch mode checks schedules automatically. |
script source
Run a named script (defined in the scripts config section) and parse its output into rows.
{
"id": "import_from_script",
