---
title: "Using FirstSales with Gemini | FirstSales"
description: "Wire Gemini to FirstSales via the REST API with narrow function tools — read before write, per-tool scopes, and safe, verifiable mutations."
canonical: "https://firstsales.io/tutorial/firstsales-with-gemini/"
---

[Home](/)/[Tutorials](/tutorial/)/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](https://app.firstsales.io)

[All tutorials](/tutorial/)