Install the CLI
The FirstSales CLI is an npm package. Install it globally to get the
firstsalesbinary:npm install -g @firstsales.io/cli firstsales --versionAuthenticate
Create a Developer API key in Settings → API, then give the CLI the key and base URL. The cleanest way is environment variables:
export FIRSTSALES_API_KEY="fs-key-..." export FIRSTSALES_BASE_URL="https://api.app.firstsales.io"Verify with whoami and doctor
Before doing anything, confirm the key resolves to the right context.
whoamishows your user, org, and workspace;doctorchecks your setup:firstsales whoami --json firstsales doctorUnderstand the global flags
Every command shares the same flags (learn these once:
--org <id>/--workspace <id>) target scope (most commands need both).--json(default) /--pretty(output format.--data '<json>'/--data-file <path>) request body.--idempotency-key <key>(safe retries on creates.--dry-run) preview the request without sending it.--confirm(required for any destructive (delete) command.--api-key/--base-url/--profile) override auth per call.
Discover your org and workspace
Most commands are scoped to an org and workspace. List them, then reuse the IDs:
firstsales organizations list firstsales workspaces list --org ORG_ID firstsales campaigns list --org ORG_ID --workspace WS_IDMake a safe first mutation
Follow the golden loop: inspect first, mutate deliberately, verify after. Preview with
--dry-run, then create with an idempotency key, then re-read to confirm:# Preview: no write happens firstsales contacts create --org ORG_ID --workspace WS_ID \ --data '{"email":"[email protected]","first_name":"Jane"}' --dry-run # Create for real, idempotently firstsales contacts create --org ORG_ID --workspace WS_ID \ --data '{"email":"[email protected]","first_name":"Jane"}' \ --idempotency-key jane-acme-2026-01 # Verify firstsales contacts list --org ORG_ID --workspace WS_IDDelete deliberately
Destructive commands refuse to run without
--confirm: this is intentional friction so an agent or a fat-fingered command can't wipe data by accident:firstsales contacts delete --org ORG_ID --workspace WS_ID \ --contact CONTACT_ID --confirm
Pro tips
Inspect first, mutate deliberately, verify after
The CLI is built around this loop. List before you change, --dry-run the change, run it, then re-read to confirm. It's slower to type and far cheaper than undoing a bad write.
--json is for machines, --pretty is for you
Output defaults to compact JSON so scripts and agents can parse it. Add --pretty when a human is reading, but keep --json in pipelines.
Never echo your key
Pass auth via FIRSTSALES_API_KEY or a profile, not inline where it lands in shell history or logs. The CLI never prints raw keys: don't undo that by echoing the env var.
Idempotency keys are free insurance
Any create that might be retried should carry --idempotency-key. A replayed command becomes a no-op instead of a duplicate.
Frequently asked questions
How do I install the CLI?
npm install -g @firstsales.io/cli, which gives you the firstsales binary. It maps directly to the public /api/v1 Developer API.
How does authentication work?
Set FIRSTSALES_API_KEY (and optionally FIRSTSALES_BASE_URL) in your environment, or pass --api-key / --base-url per command. Verify with firstsales whoami --json.
Why do commands need --org and --workspace?
Resources are scoped to an organization and workspace. List them with organizations list and workspaces list --org ORG_ID, then pass both IDs on scoped commands.
What does --dry-run do?
It prints the request the CLI would send without actually sending it: so you can confirm the body and target before a real mutation.
Why won't my delete run?
Destructive commands require the --confirm flag by design. Add --confirm once you're sure: it's the safety catch that stops accidental deletions.
Is the CLI different from the API?
It's a thin wrapper over the same public /api/v1 endpoints. The CLI adds convenience (centralized auth, stable JSON, idempotency handling, and destructive-op confirmation) so agents and scripts don't reimplement it.
Can I keep multiple accounts configured?
Yes: use --profile <name> to load a saved local profile, or override --api-key/--base-url per command to switch between orgs or environments.