gtm command follows the same contract. Learn it once and every command group —
accounts, apollo, attio, exa, parallel, and the rest — behaves predictably,
whether a person or an agent is driving.
JSON on stdout, everything else on stderr
Success output is JSON printed to stdout. Progress, warnings, and errors go to stderr. That makes every command safe to pipe:Error: and exit 1. Usage errors exit 2
(and emit a cli.usage_error telemetry event with the raw arguments).
Mutation safety is command-specific
Commands that write to external systems expose the safety control appropriate to their workflow. Commands with preview/apply behavior print the planned mutation and write nothing until--apply is supplied:
attio people add, update, and upsert
write when invoked. Their --modal-sync check|deploy|skip option controls only the
Modal deployment-parity preflight before the write; it is not a preview/apply switch.
When an accounts batch runs with --apply, a record that already exists in Attio is
reported as a conflict, not an error. Re-running a fully-loaded batch is an idempotent
no-op and exits 0. A batch exits 1 only when it created nothing and the shortfall is
not explained by conflicts — that is, at least one per-record error occurred.
The preview/apply controls are:
--apply— onaccounts batch-add-people,accounts batch-add-companies,attio companies create-attribute-type, andattio enrichment backfill-domains. Default is a preview that performs no writes.--modal-sync check|deploy|skip— onattio peoplemutations (add,update,upsert). This governs the Modal deployment-parity preflight before the mutation runs:check(default) verifies the deployed app matches your local code,deployrefreshes it first, andskipbypasses the preflight. These commands also accept--no-connectivity-probeto skip the Modal reachability check; neither option prevents the Attio write.
Remote execution on Modal
Provider-backed commands do not call provider APIs from your machine. The CLI validates your input, then invokes a function on your deployed Modal app by name (modal.Function.from_name(MODAL_APP, ...)) and prints the JSON it returns. The app name
comes from the MODAL_APP environment variable, defaulting to gtm-sdk.
This means keyed commands need a deployed app (uv run modal deploy deploy.py) and Modal
credentials — see the quickstart and
Secrets and API keys. Local-only commands (version, hello,
gmail url decode, granola export, sanity blog download, webhook list) work with
no deployment at all.
The --json override
Every command that builds a request from flags also accepts --json with a complete
payload, which overrides the flags. Useful when an agent already holds a structured
request:
1.
API-key override flags
Commands that call a provider accept a per-invocation key override (--exa-api-key, --attio-api-key, --apollo-api-key, --parallel-api-key, …). The
value is forwarded to the deployed function and applied only for that call — the
resolution order is documented in Secrets and API keys.