NewSee how
FirstSales
Home/Tutorials/Using FirstSales in CI/CD & Scripts
Developer & CLI

Using FirstSales in CI/CD & Scripts

Run the FirstSales CLI in pipelines — masked secrets, fail-fast auth, dry-run on PRs and apply on main, idempotent steps, and guarded destructive actions.

8 min read·Advanced·7 steps
  1. 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. 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 never echo-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. 3

    Fail fast on a bad key

    Start every job with whoami so 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. 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. 5

    Make retryable steps idempotent

    CI steps get retried — by flaky runners, by humans re-running a job. Derive an --idempotency-key from 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. 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 --confirm out of any step that shouldn't delete, and parse --json output to assert the result:

    firstsales connectors delete --org "$ORG" --workspace "$WS" \
      --connector "$STALE_ID" --confirm --json | jq -e '.deleted == true'
  7. 7

    Parse JSON, don't scrape

    All output is JSON by default — pipe it to jq to make decisions, set job outputs, or gate later steps. Never scrape the app UI or hit private endpoints from CI; if a command returns unsupported_operation, fail the step and surface it rather than working around it.

Pro tips

Hard-won shortcuts that keep warm-up on track.

1

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.

2

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.

3

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.

4

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