Skip to content
NewSee how

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.

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

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

  • Idempotency keys are free insurance

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

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

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

Getting Started

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.