NewSee how
FirstSales
Home/Tutorials/RevOps playbook: automate outbound with API keys, the CLI & CSV
Getting Started

RevOps playbook: automate outbound with API keys, the CLI & CSV

Mint scoped API keys, import and sync contacts programmatically, drive campaigns from the CLI, wire it into CI/CD, and monitor key usage and credit runway — using the REST API and CSV, the surface that's live today.

14 min read·Advanced·8 steps
  1. 1

    Frame the automatable surface (and what isn't yet)

    What's live today: the REST API, the CLI, and CSV/JSON contact import/export. What isn't: webhooks, CRM connectors (Salesforce, HubSpot, Zoho, Pipedrive), and Slack/Teams/Sheets/Airtable/Notion connectors are all coming-soon and return unsupported_operation if you try to call them. Build automation on the live surface — poll and export via REST/CSV instead of waiting on a connector.

  2. 2

    Mint a least-privilege API key

    Go to Settings → API and create one named key per consumer — e.g. ci-contact-sync — with only the scopes it needs (contacts:read/write, campaigns:read, etc.). The key is shown once; copy it straight into a secret manager. Revoke instantly if it ever leaks — revocation is immediate. See Create & Manage Developer API Keys.

    Mint a least-privilege API key
  3. 3

    Install and verify the CLI

    Install the CLI and confirm the key resolves before doing anything else:

    npm install -g @firstsales.io/cli
    export FIRSTSALES_API_KEY="fs_live_..."   # from your secret manager
    firstsales whoami --json
    firstsales doctor
  4. 4

    Import contacts programmatically from CSV

    Push a batch through an import job with an idempotency key — the platform de-duplicates by email server-side, so a re-run of the same file won't duplicate anyone:

    firstsales contact-imports create --org "$ORG_ID" --workspace "$WS_ID" \
      --data-file ./leads.csv \
      --idempotency-key "leads-import-2026-07"
  5. 5

    Keep contacts in sync — the golden loop

    Automation follows the same loop as a human operator: inspect, then mutate deliberately, then verify. Preview with --dry-run before any write, mutate with an idempotency key, then re-read to confirm:

    firstsales contacts list --org "$ORG_ID" --workspace "$WS_ID" --json
    firstsales contacts update --org "$ORG_ID" --workspace "$WS_ID" \
      --contact "$CONTACT_ID" --data '{"job_title":"VP Sales"}' --dry-run
    firstsales contacts update --org "$ORG_ID" --workspace "$WS_ID" \
      --contact "$CONTACT_ID" --data '{"job_title":"VP Sales"}'
    firstsales contacts get --org "$ORG_ID" --workspace "$WS_ID" --contact "$CONTACT_ID"
  6. 6

    Drive campaigns from the CLI or API

    List and read campaigns through the CLI, or hit /api/v1 directly with a Bearer token when you'd rather not shell out to the CLI:

    firstsales campaigns list --org "$ORG_ID" --workspace "$WS_ID" --json
    
    curl https://api.app.firstsales.io/api/v1/organizations/$ORG_ID/workspaces/$WS_ID/campaigns/$CAMPAIGN_ID/analytics \
      -H "Authorization: Bearer $FIRSTSALES_API_KEY"
  7. 7

    Wire it into CI/CD

    Inject the key from your CI provider's secret store, never hardcode it. Start every job with whoami so a bad key fails fast, and gate real writes behind --dry-run on PRs vs. real mutation on main, with idempotency keys derived from a stable CI variable so a replayed pipeline step is a no-op:

    # GitHub Actions
    - name: FirstSales sync
      env:
        FIRSTSALES_API_KEY: ${{ secrets.FIRSTSALES_API_KEY }}
      run: |
        firstsales whoami --json > /dev/null || { echo "auth failed"; exit 1; }
        firstsales contact-imports create --org "$ORG_ID" --workspace "$WS_ID" \
          --data-file ./leads.csv --idempotency-key "import-${GITHUB_SHA}"
  8. 8

    Monitor usage and credit runway

    Watch API-key usage/logs under Settings → API and the org's credit balance under Billing so an automated pipeline never silently runs the org dry. See Credits, Plans & Billing and Read Campaign Analytics.

Pro tips

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

1

One scoped key per consumer

CI, your laptop, and each integration each get their own named key — leak or retire one and you revoke just that key, not everything.

2

Idempotency keys are free insurance

Every automated create carries --idempotency-key so a retried pipeline step is a no-op, not a duplicate record.

3

Don't build against coming-soon connectors

Webhooks, CRM, Slack, and Sheets connectors return unsupported_operation today — poll or export via REST + CSV instead of waiting on them.

4

Inspect, dry-run, mutate, verify — in scripts too

The same loop that protects a human operator protects an agent. Always --dry-run before any write in automation, not just interactively.

Frequently asked questions

What can I actually automate today?

The REST API, the CLI, and CSV/JSON contact import/export, all authenticated with scoped Developer API keys.

Are there webhooks, CRM, or Slack integrations?

Not yet — webhooks, CRM connectors (Salesforce, HubSpot, Zoho, Pipedrive), and Slack/Teams/Sheets/Airtable/Notion connectors are coming-soon and return unsupported_operation. Don't build automation that depends on them.

How do I import contacts programmatically?

contact-imports create --data-file with a CSV or JSON file. Contacts are de-duplicated by email server-side, so re-running the same import is safe.

How does auth work for CI?

Store the Developer API key in your CI provider's secret store and expose it as FIRSTSALES_API_KEY. For raw HTTP, send it as a Bearer token.

How do I avoid duplicate writes in a pipeline?

Pass --idempotency-key (or the Idempotency-Key header on raw HTTP) derived from a stable value like a commit SHA — a retried step becomes a no-op instead of a duplicate.

How do I keep a delete from wiping data in CI?

Destructive CLI commands require --confirm — keep it out of any step that shouldn't delete, so a cleanup job can only remove data when the flag is explicitly present.

How do I watch usage and credit runway?

Check API-key usage/logs under Settings → API and the org's credit balance under Billing so an automated pipeline doesn't silently run you out of credits.

CLI or raw API?

The CLI is a thin wrapper over the same public /api/v1 surface. Either works — the CLI adds convenient auth handling, idempotency, and JSON output for scripts and agents.

Ready to put this into practice?

Start your FirstSales trial and launch a warmed, authenticated mailbox in minutes.

Start for $1