NewSee how
FirstSales
Home/Tutorials/Using FirstSales with Gemini
Developer & CLI

Using FirstSales with Gemini

Wire Gemini to FirstSales via the REST API with narrow function tools — read before write, per-tool scopes, and safe, verifiable mutations.

8 min read·Advanced·6 steps
  1. 1

    Two ways to wire up Gemini

    If your Gemini environment (e.g. the Gemini CLI with shell access) can run npm, install @firstsales.io/cli and expose it as a tool — it handles auth, JSON, and idempotency. If Gemini runs API-only, call the FirstSales Developer API over HTTPS from your function/tool implementations. The docs place Gemini with the API-first agents by default.

  2. 2

    Store the key outside the model

    Create a Developer API key in Settings → API and keep it in your app's server-side config as FIRSTSALES_API_KEY. Gemini should call a tool/function that reads the key server-side — the model itself must never see or emit the raw key.

    FIRSTSALES_API_KEY=fs-key-...
    FIRSTSALES_BASE_URL=https://api.app.firstsales.io
  3. 3

    Define narrow tools, not a raw HTTP escape hatch

    Give Gemini a small set of purpose-built functions — whoami, list_contacts, create_contact, campaign_analytics — each mapping to one public endpoint. This keeps the model on the supported surface instead of letting it construct arbitrary URLs.

    # what each tool calls under the hood
    GET  /api/v1/whoami
    GET  /api/v1/organizations/{org}/workspaces/{ws}/contacts
    POST /api/v1/organizations/{org}/workspaces/{ws}/contacts     # Idempotency-Key
    GET  /api/v1/organizations/{org}/workspaces/{ws}/campaigns/{id}/analytics
  4. 4

    Verify identity in the first tool call

    Have the agent call whoami before any write so it confirms the org and workspace context:

    curl https://api.app.firstsales.io/api/v1/whoami \
      -H "Authorization: Bearer $FIRSTSALES_API_KEY"
  5. 5

    Write idempotently from your create tool

    Your create_contact tool should always attach an Idempotency-Key so a retried function call can't double-write:

    curl -X POST \
      https://api.app.firstsales.io/api/v1/organizations/ORG_ID/workspaces/WS_ID/contacts \
      -H "Authorization: Bearer $FIRSTSALES_API_KEY" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: gemini-lead-2026-01" \
      -d '{"email":"lead@acme.com","first_name":"Sam"}'
  6. 6

    Handle errors and stay on the public surface

    In each tool, handle 401 (bad/revoked key), 422 (validation — surface the field), and 429 (back off, then retry). On unsupported_operation, return the limitation to the model and stop — never let Gemini scrape the app or hit private routes, callbacks, pixels, or cron endpoints.

Pro tips

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

1

Wrap endpoints as narrow tools

Don't hand Gemini a generic 'call any URL' function. Purpose-built tools (create_contact, campaign_analytics) keep the model on the public surface and make its actions auditable.

2

The model never touches the key

Read FIRSTSALES_API_KEY server-side inside the tool. If the key can appear in the model's context or output, it can leak into logs.

3

whoami as the first function call

Make identity verification the opening move so Gemini always confirms org/workspace before it writes.

4

Shell available? The CLI is less code

If the Gemini CLI has shell + npm, shelling out to firstsales beats hand-rolling HTTP tools — auth, JSON, idempotency, and delete-confirmation come for free.

Frequently asked questions

Does Gemini use the CLI or the API?

By default the Developer API, wrapped as functions/tools. If your Gemini environment has shell access with npm, the CLI is a lower-effort alternative that handles auth and idempotency for you.

How do I keep the key away from the model?

Store FIRSTSALES_API_KEY in server-side config and read it inside the tool implementation. The model calls the tool; it never sees or emits the raw key.

How should I structure the tools?

As narrow, single-purpose functions — one per public endpoint (whoami, list/create contact, campaign analytics). Avoid a generic 'fetch any URL' tool that lets the model leave the supported surface.

How do I avoid duplicate writes?

Attach an Idempotency-Key header inside your create tool. A retried function call with the same key returns the original result rather than creating a duplicate.

What's off-limits?

App-private routes, provider callbacks, tracking pixels, unsubscribe handlers, internal cron endpoints, and scraping the UI. Stay on public /api/v1; stop on unsupported_operation.

Ready to put this into practice?

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

Start for $1