Metadata-Version: 2.4
Name: autotouch-cli
Version: 0.2.9
Summary: Autotouch Smart Table CLI
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: requests>=2.31.0
Requires-Dist: python-dotenv>=1.0.0

# Autotouch CLI Reference (`autotouch`)

This page documents the installable CLI for the Smart Table developer API.
Use it when you want less boilerplate than raw HTTP/cURL while keeping the same API behavior, auth, and scopes.

## Install

```bash
pipx install autotouch-cli
# or
pip install autotouch-cli
```

## Configure auth

Developer keys and scopes are identical to raw API usage (`stk_...`, same scope checks).

```bash
autotouch auth set-key --api-key stk_... --base-url https://app.autotouch.ai
autotouch auth check
autotouch auth show
```

Credentials are stored in `~/.config/autotouch/config.json` by default.
Override path with `AUTOTOUCH_CONFIG_PATH`.

## API endpoint -> CLI command map

| API endpoint | CLI command |
| --- | --- |
| `GET /api/capabilities` | `autotouch capabilities` |
| `POST /api/tables` | `autotouch tables create --name "My Table"` |
| `GET /api/tables?view_mode=org` | `autotouch tables list --view-mode org` |
| `POST /api/tables/{table_id}/rows` | `autotouch rows add --table-id <TABLE_ID> --records-file rows.json` |
| `POST /api/tables/{table_id}/import-optimized` | `autotouch rows import-csv --table-id <TABLE_ID> --file contacts.csv` |
| `GET /api/tables/{table_id}/import-status/{task_id}` | `autotouch rows import-status --table-id <TABLE_ID> --task-id <TASK_ID>` |
| `PATCH /api/tables/{table_id}/cells` | `autotouch cells patch --table-id <TABLE_ID> --updates-file updates.json` |
| `GET /api/tables/{table_id}/columns` | `autotouch columns list --table-id <TABLE_ID>` |
| `POST /api/tables/{table_id}/columns` | `autotouch columns create --table-id <TABLE_ID> --data-file column.json` |
| `PATCH /api/tables/{table_id}/columns/{column_id}` | `autotouch columns update --table-id <TABLE_ID> --column-id <COLUMN_ID> --data-file column-update.json` |
| `POST /api/tables/{table_id}/columns/projections` | `autotouch columns projections --table-id <TABLE_ID> --data-file projections.json` |
| `POST /api/tables/{table_id}/columns/{column_id}/estimate` | `autotouch columns estimate --table-id <TABLE_ID> --column-id <COLUMN_ID> --scope all` |
| `POST /api/tables/{table_id}/columns/{column_id}/run` | `autotouch columns run --table-id <TABLE_ID> --column-id <COLUMN_ID> --scope all` |
| `GET /api/bulk-jobs/{job_id}` | `autotouch jobs get --job-id <JOB_ID>` |
| `GET /api/tables/{table_id}/webhook` | `autotouch webhooks get --table-id <TABLE_ID>` |
| `POST /api/tables/{table_id}/webhook` | `autotouch webhooks rotate --table-id <TABLE_ID>` |
| `POST /api/webhooks/tables/{table_id}/ingest` | `autotouch webhooks ingest --table-id <TABLE_ID> --records-file records.json --webhook-token <WEBHOOK_TOKEN>` |

## Column create payload recipes (CLI-ready)

All examples below are used with:

```bash
autotouch columns create --table-id <TABLE_ID> --data-file <payload.json>
```

### 1) `email_finder`

`email-finder.json`:

```json
{
  "key": "work_email",
  "label": "Work Email",
  "kind": "enrichment",
  "dataType": "json",
  "origin": "email_finder",
  "autoRun": "never",
  "config": {
    "provider": "smart_email_finder",
    "strategy": "cost_optimized",
    "lookupStrategy": "linkedin",
    "linkedinOnly": true,
    "enableProfileFallback": false,
    "linkedinUrl": "linkedin_url"
  }
}
```

### 2) `phone_finder`

`phone-finder.json`:

```json
{
  "key": "mobile_phone",
  "label": "Mobile Phone",
  "kind": "enrichment",
  "dataType": "json",
  "origin": "phone_finder",
  "autoRun": "never",
  "config": {
    "provider": "smart_phone_finder",
    "firstName": "first_name",
    "lastName": "last_name",
    "company": "company",
    "linkedinUrl": "linkedin_url"
  }
}
```

### 3) `lead_finder`

`lead-finder.json`:

```json
{
  "key": "lead_contacts",
  "label": "Lead Contacts",
  "kind": "enrichment",
  "dataType": "json",
  "origin": "ai",
  "autoRun": "never",
  "config": {
    "provider": "lead_finder",
    "sourceMode": "bulk_companies",
    "companyDomain": "domain",
    "strictness": "flexible_roles",
    "storeAsLeads": true,
    "autoEnrichEmails": true,
    "autoEnrichPhones": false,
    "jobTitles": ["Head of Sales", "VP Sales"],
    "locations": ["United States"],
    "maxResults": 10
  }
}
```

### 4) `llm_enrichment`

`llm-enrichment.json`:

```json
{
  "key": "company_research",
  "label": "Company Research",
  "kind": "enrichment",
  "dataType": "json",
  "origin": "ai",
  "autoRun": "never",
  "config": {
    "prompt": "Research this company and return JSON with icp_fit, summary, and risks.",
    "mode": "agent",
    "temperature": 0.7,
    "batchSize": 50,
    "promptSource": "manual",
    "structuredOutput": true,
    "useAutoSchema": true
  }
}
```

### 5) `add_to_crm`

`add-to-crm.json`:

```json
{
  "key": "add_to_leads",
  "label": "Add to Leads",
  "kind": "enrichment",
  "dataType": "json",
  "origin": "manual",
  "autoRun": "onSourceUpdate",
  "config": {
    "provider": "add_to_crm",
    "leadSource": "research_table_export",
    "fieldMappings": {
      "mode": "single",
      "linkedinUrl": "linkedin_url",
      "companyDomain": "domain",
      "firstName": "first_name",
      "lastName": "last_name",
      "title": "title",
      "companyName": "company",
      "emailAddresses": [
        { "column": "work_email", "type": "work" }
      ],
      "phoneNumbers": [
        { "column": "mobile_phone", "type": "mobile" }
      ]
    },
    "sourceColumns": [
      "linkedin_url",
      "domain",
      "first_name",
      "last_name",
      "title",
      "company",
      "work_email",
      "mobile_phone"
    ]
  }
}
```

Add-to-Leads note: mapped LinkedIn URL + company domain are required; run is non-billable.

## Common workflow

```bash
autotouch capabilities
autotouch tables create --name "CLI Contacts"
autotouch rows import-csv --table-id <TABLE_ID> --file contacts.csv
autotouch columns create --table-id <TABLE_ID> --data-file column.json
autotouch columns run --table-id <TABLE_ID> --column-id <COLUMN_ID> --scope filtered --filters-file filters.json --unprocessed-only --show-estimate --wait
autotouch jobs get --job-id <JOB_ID>
```

## Safe run patterns (`firstN` + `--unprocessed-only`)

Use this pattern for progressive rollouts.

```bash
# Pilot first 10 rows
autotouch columns run \
  --table-id <TABLE_ID> \
  --column-id <COLUMN_ID> \
  --scope firstN \
  --first-n 10 \
  --unprocessed-only \
  --show-estimate \
  --wait

# Extend to first 15 rows (processes the next 5 if first 10 are already done)
autotouch columns run \
  --table-id <TABLE_ID> \
  --column-id <COLUMN_ID> \
  --scope firstN \
  --first-n 15 \
  --unprocessed-only \
  --show-estimate \
  --wait
```

Notes:
- `firstN` without `--unprocessed-only` can re-run already-processed rows.
- With `--unprocessed-only`, `firstN` means "first N currently eligible unprocessed rows", not "exactly N new rows since your last check".
- If you need an exact count (for example exactly 5 rows), use `run-next` below.
- `--wait` polls `/api/bulk-jobs/{job_id}` until terminal status.
- If a job stays `queued`, workers for that provider queue may be scaled to `0`.

## Exact count runs (`run-next`)

Use this when you need exactly `N` rows in one run.
The CLI selects candidate row IDs first, then executes `/run` with `scope=subset`.

```bash
# Run exactly 5 unprocessed rows from the current view
autotouch columns run-next \
  --table-id <TABLE_ID> \
  --column-id <COLUMN_ID> \
  --count 5 \
  --filters-file filters.json \
  --show-estimate \
  --wait
```

Notes:
- Default behavior is unprocessed-only selection.
- Add `--include-processed` to allow already-processed rows into candidate selection.
- `run-next` is deterministic on count (subject to available eligible rows).
- If fewer than `N` eligible rows exist, it runs the available subset and reports selected count.

### Agent execution contract (strict)

When operating this CLI as an agent, use backend job state as source of truth:

1. Treat a run as started only if `/run` returns a `jobId` (`job_id`).
2. Treat a run as completed only when `GET /api/bulk-jobs/{job_id}` returns terminal status.
3. Never infer progress/completion from local process liveness alone.
4. If polling is blocked by local network/approval/sandbox constraints, report "run state not confirmed" (do not claim still running/completed).

Terminal status values:

- `completed`
- `partial`
- `error`
- `cancelled`

Non-terminal status values:

- `queued`
- `distributing`
- `processing`

### Canonical fallback (when `--wait` is noisy in your runtime)

```bash
# 1) Queue run and capture jobId
autotouch columns run \
  --table-id <TABLE_ID> \
  --column-id <COLUMN_ID> \
  --scope firstN \
  --first-n 15 \
  --unprocessed-only \
  --show-estimate \
  --output json

# 2) Poll backend truth directly
autotouch jobs get --job-id <JOB_ID> --output json
```

Repeat `jobs get` until status is terminal.

## CSV import (agent-safe, async-first)

`rows import-csv` defaults to optimized import transport (`/import-optimized`) so large files do not fail on a single long request.

```bash
# Queue background import and return task_id quickly
autotouch rows import-csv \
  --table-id <TABLE_ID> \
  --file contacts.csv

# Wait for completion
autotouch rows import-status \
  --table-id <TABLE_ID> \
  --task-id <TASK_ID> \
  --wait
```

Notes:
- Use `--sync` only when you explicitly want synchronous behavior.
- Legacy direct path is still available with `--transport direct`.
- Optimized import emits progressive events while processing, and starts with a small first batch for fast initial row visibility.

## Capabilities for agents

Use capabilities as the source of truth before generating payloads:

```bash
autotouch capabilities
```

Agent expectations:

- `column_types` tells you which column types are runnable and which are non-billable transforms (`json_split`, `formatter_formula`).
- `filtering` describes valid scope/filter semantics for estimate/run.
- `automation.auto_run` describes supported auto-run modes + config field names.
- `execution_policies.llm.output_contract` describes output behavior by mode:
  - `agent` => JSON-oriented structured output
  - `basic` => text or JSON (`dataType=json` for structured JSON output)
- `webhooks.table_ingest` describes webhook ingest auth contract (metadata only; no secret tokens).

## JSON output pipeline pattern

For enrichment responses that return structured JSON, use this chain:

1. Run enrichment into a JSON column.
2. Split JSON into projection columns.
3. Optional formatter normalization.
4. Feed extracted/normalized keys into downstream enrichment columns.

Important mode distinction:

- Agent mode is JSON-oriented and is intended for structured outputs.
- Basic mode can return plain text or JSON depending on your column `dataType`/schema setup.

```bash
# Create projections from a JSON source column
autotouch columns projections \
  --table-id <TABLE_ID> \
  --data-file projections.json

# Optional: update downstream column config to reference projected keys
autotouch columns update \
  --table-id <TABLE_ID> \
  --column-id <DOWNSTREAM_COLUMN_ID> \
  --data-file column-update.json
```

`projections.json`:

```json
{
  "items": [
    {
      "key": "person_name",
      "label": "Person Name",
      "sourceColumnId": "<JSON_COLUMN_ID>",
      "path": "person_name",
      "dataType": "text"
    },
    {
      "key": "school_domain",
      "label": "School Domain",
      "sourceColumnId": "<JSON_COLUMN_ID>",
      "path": "school_domain",
      "dataType": "text"
    }
  ]
}
```

## Filtering (credit control)

Use `scope=filtered` to run only matching rows.

`filters.json`:

```json
{
  "mode": "and",
  "filters": [
    { "columnKey": "linkedin_url", "operator": "isNotEmpty" },
    { "columnKey": "country", "operator": "equals", "value": "United States" }
  ]
}
```

```bash
# Estimate first (non-billable)
autotouch columns estimate \
  --table-id <TABLE_ID> \
  --column-id <COLUMN_ID> \
  --scope filtered \
  --filters-file filters.json \
  --unprocessed-only

# Run same payload with rollout cap
autotouch columns run \
  --table-id <TABLE_ID> \
  --column-id <COLUMN_ID> \
  --scope filtered \
  --filters-file filters.json \
  --unprocessed-only \
  --first-n 200 \
  --show-estimate --wait
```

### Cost tip: filter out empty rows between enrichments

Most teams run paid enrichments only on rows that already have required upstream data.
This avoids spending credits on rows that cannot produce useful results yet.

Example: run email finder only when `linkedin_url` exists and `work_email_address` is still empty.

```json
{
  "mode": "and",
  "filters": [
    { "columnKey": "linkedin_url", "operator": "isNotEmpty" },
    { "columnKey": "work_email_address", "operator": "isEmpty" }
  ]
}
```

Pattern to reuse:
- Step 1: create/select a filter that excludes empty prerequisite fields.
- Step 2: run small (`firstN` or `run-next`) with `--show-estimate`.
- Step 3: expand only after output quality looks good.

## Auto-run configuration

Auto-run is set on the column definition (`autoRun`) and can be changed later with `columns update`.

`column-update.json`:

```json
{
  "autoRun": "onInsert",
  "config": {
    "autoRunMode": "conditional",
    "autoRunFilters": {
      "mode": "and",
      "filters": [
        { "columnKey": "linkedin_url", "operator": "isNotEmpty" }
      ]
    }
  }
}
```

```bash
autotouch columns update \
  --table-id <TABLE_ID> \
  --column-id <COLUMN_ID> \
  --data-file column-update.json
```

## Table webhooks (ingest)

Webhook ingestion uses a per-table token, not developer API key scopes.

```bash
# Read current webhook config
autotouch webhooks get --table-id <TABLE_ID>

# Create/rotate token (token is returned once)
autotouch webhooks rotate --table-id <TABLE_ID>
```

`records.json`:

```json
{
  "records": [
    { "first_name": "Ada", "email": "ada@example.com" }
  ]
}
```

```bash
# Send records with webhook token
autotouch webhooks ingest \
  --table-id <TABLE_ID> \
  --records-file records.json \
  --webhook-token <WEBHOOK_TOKEN>
```

## Budget and safety controls

```bash
# Estimate only (no execution)
autotouch columns run --table-id <TABLE_ID> --column-id <COLUMN_ID> --scope filtered --filters-file filters.json --unprocessed-only --dry-run

# Guard against overspend
autotouch columns run --table-id <TABLE_ID> --column-id <COLUMN_ID> --scope filtered --filters-file filters.json --unprocessed-only --max-credits 50

# Poll until terminal status
autotouch jobs watch --job-id <JOB_ID>
```

## Notes

- The CLI is a thin wrapper around the same HTTP endpoints documented in `docs/research-table/reference/tables-api.md`.
- If a key is missing scope, CLI commands fail the same way raw API calls do (`403`).
- Use `--base-url` and `--token` per command for CI/ephemeral environments.
