Generate a Developer API key
In the app go to Settings → API and create a Developer API key. Set its scopes and access level (read vs read-write) to the minimum the integration needs. The secret is shown once: copy it into a secret manager immediately; the app only stores a redacted form.
Revoke and regenerate on the same page if a key ever leaks.
Verify identity with whoami
The base URL is
https://api.app.firstsales.io. Auth is a Bearer token. Always start by confirming the key resolves to the org and workspace you expect:GET /api/v1/whoami:curl https://api.app.firstsales.io/api/v1/whoami \ -H "Authorization: Bearer $FIRSTSALES_API_KEY"Find your org and workspace IDs
Every resource is scoped to an organization and a workspace, so most paths look like
/api/v1/organizations/{orgId}/workspaces/{workspaceId}/…. List them:# Organizations you can access curl https://api.app.firstsales.io/api/v1/organizations \ -H "Authorization: Bearer $FIRSTSALES_API_KEY" # Workspaces inside an org curl https://api.app.firstsales.io/api/v1/organizations/ORG_ID/workspaces \ -H "Authorization: Bearer $FIRSTSALES_API_KEY"Create a contact (idempotently)
POST a contact into a workspace. Pass an Idempotency-Key header so a retried request never creates a duplicate: safe to replay on network failure.
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: jane-acme-2026-01" \ -d '{ "email": "[email protected]", "first_name": "Jane", "company_name": "Acme" }'Read campaign analytics
Pull funnel and engagement metrics for a campaign to sync results into your own reporting: no scraping the dashboard:
curl https://api.app.firstsales.io/api/v1/organizations/ORG_ID/workspaces/WS_ID/campaigns/CAMPAIGN_ID/analytics \ -H "Authorization: Bearer $FIRSTSALES_API_KEY"Read replies from the inbox
List inbox threads to pull sends, opens, replies, and bounces into your systems. Thread bodies are redacted in the list view; fetch a single thread for detail. This is how you route positive replies into a CRM while native connectors are still coming.
curl https://api.app.firstsales.io/api/v1/organizations/ORG_ID/workspaces/WS_ID/inbox/threads \ -H "Authorization: Bearer $FIRSTSALES_API_KEY"Stay inside the public surface
The API only exposes the public
/api/v1surface. Don't call app-private routes, provider callbacks, tracking pixels, unsubscribe handlers, or internal cron routes: they aren't part of the contract and will break. If a call returnsunsupported_operation, stop and treat it as "not available via the API," not something to work around.Standard status codes apply:
401bad/revoked key,422validation (body names the field),429rate-limited (back off and retry).
Pro tips
whoami before you write
Confirm the key resolves to the org/workspace you intend before any mutating call. A key scoped to the wrong workspace is the most common way to write data into the wrong place.
Always send an Idempotency-Key on creates
Network retries happen. An Idempotency-Key makes a replayed POST a no-op instead of a duplicate contact or double charge.
Store the key once, never print it
The secret is shown a single time and stored redacted. Put it straight into a secret manager; never log it, commit it, or ship it in a client bundle.
Treat unsupported_operation as a hard stop
If the API returns unsupported_operation, the thing you want isn't in the public contract. Don't reach for app-private routes to force it: report the limitation.
Frequently asked questions
Where do I get an API key?
Settings → API. Create a Developer API key with the scopes and access level you need; it's shown once, so copy it immediately. Revoke and regenerate on the same page if it's exposed.
What's the base URL and auth?
Base URL https://api.app.firstsales.io; authenticate with Authorization: Bearer $FIRSTSALES_API_KEY and send JSON. Start with GET /api/v1/whoami to confirm context.
Why are the paths so long?
Resources are scoped to an org and workspace, so paths are /api/v1/organizations/{orgId}/workspaces/{workspaceId}/…. Get the IDs from /api/v1/organizations and its /workspaces sub-resource (or from whoami).
How do I avoid duplicate writes?
Send an Idempotency-Key header on create requests. A retried request with the same key returns the original result instead of creating a second record: safe to replay after a timeout.
Can I read campaign metrics and replies?
Yes: GET …/campaigns/{id}/analytics for funnel/engagement, and GET …/inbox/threads for conversation activity. Thread bodies are redacted in the list; fetch a single thread for full detail.
Is there a Zapier or CRM integration instead?
No Zapier connector, and native CRM connectors (Salesforce, HubSpot, Pipedrive) are still coming. Until they ship, the REST API (or the CLI) is the supported way to integrate and sync.
What does unsupported_operation mean?
The requested action isn't part of the public /api/v1 contract. Stop and report it: don't call app-private routes, callbacks, pixels, or internal endpoints to work around it; those aren't supported and will break.