Skip to content
NewSee how

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.

  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 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. 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. 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-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. 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. 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

  • 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.

Developer & CLI

Outreach that keeps going after you log off.Leads, emails, follow-ups and replies, handled.

Connect unlimited mailboxes and let AI agents find leads, write each email and follow up. Or send your own templates in manual mode. From $29/mo.

$1 for 3 days · Cancel anytime · No contract

Install with AI

Your coding agent sets up the FirstSales CLI and skills for you.

Prompt for Claude Code

I'm setting up FirstSales (https://firstsales.io), an AI outbound sales platform: campaigns, contacts, a reply inbox, deliverability and lead signals. Help me get my workspace sending real, good emails end to end, from Claude Code.

Read these first so you know how FirstSales works:
- Developer docs and API: https://developer.firstsales.io
- CLI: https://www.npmjs.com/package/@firstsales.io/cli
- Skills for coding agents: https://github.com/firstsalesio/firstsales-skills (read README.md and GUIDE.md)

Setup
1. Install the CLI: npm install -g @firstsales.io/cli
2. Install the FirstSales skills:
   claude plugin marketplace add firstsalesio/firstsales-skills
   claude plugin install firstsales@firstsales
3. Ask me if I already have a FirstSales account. If not, send me to https://app.firstsales.io to sign up.
4. Ask me to create an API key under Settings → API → API Keys, with only the scopes we need.
   Never ask me to paste the key into this chat. Tell me to run this in my own terminal, so the key stays hidden:
   read -rs FS_KEY && firstsales auth login --api-key "$FS_KEY"; unset FS_KEY
5. Run `firstsales whoami` and tell me which workspace and organisation we are connected to.

Then ask me about my business: what I sell, who I sell to (roles, company size, countries), my best customers, and what I use today for outbound (tools, lists, mailboxes). Walk me through:
1. Teaching FirstSales my business: website, offer, proof points and case studies in the knowledge base, so the AI writes like someone who knows the product.
2. Connecting my sending mailboxes and checking my sending domain: SPF, DKIM, DMARC and warm-up. If anything is unhealthy, tell me the exact DNS record to add.
3. Bringing in contacts: import my CSV or CRM export into a clean list, remove duplicates and bad emails, and block domains I must never email (customers, competitors, my own).
4. Building my first campaign: the right list, senders, steps and timing. Write the sequence with me, show me the drafts, and do not launch until I say yes.
5. Setting up Signals if it fits: find people engaging with LinkedIn posts about topics my buyers care about, and turn them into leads.
6. Inviting my team and giving each person the right access.

Once campaigns run, show me how to work the reply inbox from here: which replies need an answer, draft responses in my voice, and move good conversations into deals. If I use Cal.com, connect it so follow-ups can offer my booking link (not the first email).

Throughout, as you learn about my business, proactively suggest and help create:
- Reply templates for the questions and objections my prospects are likely to raise, based on my product, site and codebase if you can see them.
- Campaign ideas and segments worth testing, with the reason for each.

Safety rules:
- Show every change as a dry run (--dry-run) first and wait for my yes. Never send emails, launch campaigns or delete anything without my yes.
- Tell me before anything that spends credits, and how many.

Be opinionated. If the docs give a sensible default, just use it. Don't make me choose between five options.