- 1
Prefer the CLI in pipelines
For CI/CD jobs and local scripts, the docs recommend the CLI over raw HTTP — it centralizes credential handling, emits stable JSON your steps can parse, and keeps the destructive-operation safeguards you want in automation. One tool, one auth path, consistent output.
- 2
Inject the key as a masked secret
Store your Developer API key in your CI provider's secret store (GitHub Actions secrets, GitLab CI variables, etc.) and expose it as
FIRSTSALES_API_KEY. Never hardcode it in the workflow file. Most CI systems mask secret values in logs automatically — keep it that way by neverecho-ing the key.# GitHub Actions job step - name: FirstSales sync env: FIRSTSALES_API_KEY: ${{ secrets.FIRSTSALES_API_KEY }} run: | npm install -g @firstsales.io/cli firstsales whoami --json - 3
Fail fast on a bad key
Start every job with
whoamiso the pipeline stops immediately if the key is wrong or revoked, instead of half-running a sync:firstsales whoami --json > /dev/null || { echo "FirstSales auth failed"; exit 1; } - 4
Plan with --dry-run, then apply
In automation you want a preview before you write. Gate real mutations behind a plan step using
--dry-run(e.g. on pull requests), and only apply on the main branch:# PR / plan: preview only firstsales contacts create --org "$ORG" --workspace "$WS" \ --data-file ./new-lead.json --dry-run # main / apply: real write, idempotent firstsales contacts create --org "$ORG" --workspace "$WS" \ --data-file ./new-lead.json \ --idempotency-key "lead-${GITHUB_SHA}" - 5
Make retryable steps idempotent
CI steps get retried — by flaky runners, by humans re-running a job. Derive an
--idempotency-keyfrom something stable (a commit SHA, a row ID, a run number) so a re-run doesn't double-create:firstsales contact-imports create --org "$ORG" --workspace "$WS" \ --data-file ./import.json \ --idempotency-key "import-${CI_PIPELINE_ID}" - 6
Guard destructive steps
Deletes require
--confirm, which is exactly what you want in a pipeline: a cleanup job can only remove data when the flag is explicitly present. Keep--confirmout of any step that shouldn't delete, and parse--jsonoutput to assert the result:firstsales connectors delete --org "$ORG" --workspace "$WS" \ --connector "$STALE_ID" --confirm --json | jq -e '.deleted == true' - 7
Parse JSON, don't scrape
All output is JSON by default — pipe it to
jqto make decisions, set job outputs, or gate later steps. Never scrape the app UI or hit private endpoints from CI; if a command returnsunsupported_operation, fail the step and surface it rather than working around it.
Pro tips
Hard-won shortcuts that keep warm-up on track.
whoami as the first job step
A one-line auth check at the top of the job turns a bad/expired key into an instant, obvious failure instead of a partial, confusing one.
Derive idempotency keys from CI variables
Commit SHA, pipeline ID, run number — any stable per-run value makes a retried step a safe no-op. Never use a timestamp or random value; those defeat the purpose.
Two-phase: dry-run on PRs, apply on main
Mirror infra-as-code. --dry-run on pull requests shows the diff for review; the real write only runs after merge. Cheap safety for irreversible actions.
Assert on JSON output
Pipe --json to jq -e so the step actually fails when the result isn't what you expected, instead of exiting 0 on a soft error.
Frequently asked questions
Should CI use the CLI or the API?
The CLI. For CI/CD and scripts it's recommended over raw HTTP because it standardizes credential handling and output and keeps the destructive-op safeguards built in.
How do I handle the API key in CI?
Put it in your CI provider's secret store and expose it as FIRSTSALES_API_KEY. Don't hardcode it in the workflow, and never echo it — most CI systems mask secrets in logs only if you don't print them yourself.
How do I preview a change before it runs?
Use --dry-run to print the request without sending it. A common pattern is dry-run on pull requests and the real write only on the main branch.
How do I make a step safe to retry?
Pass --idempotency-key derived from a stable value like the commit SHA or pipeline ID. A retried step with the same key is a no-op instead of a duplicate write.
How do I stop a cleanup job from over-deleting?
Deletes require --confirm, so a step can only remove data when the flag is explicitly present. Keep it off non-destructive steps, and assert on the --json result with jq -e.
Can I make decisions from the output?
Yes — output is JSON by default. Pipe it to jq to gate later steps or set job outputs. Don't scrape the UI; on unsupported_operation, fail the step and report it.
Ready to put this into practice?
Start your FirstSales trial and launch a warmed, authenticated mailbox in minutes.
Start for $1