Instantly is where sequences actually go out. Your agent builds the list, loads it into a campaign, and stops. Nothing sends until you approve it.
Instantly is the last step. Sourcing and enrichment feed it a finished list; it sends, and reports back.
validate first, the sending reputation is yours
the list that fills the campaign
turn a reply into a record instead of a screenshot
the call that follows a positive reply
A skill is a whole pipeline your agent already knows. These ones call Instantly as part of the run.
Sweep Layoff Signal news, source displaced talent via GitHub + Apollo, enrich via Prospeo, push to outreach.
Top GitHub contributors at top companies. Enrich via Prospeo for a never-resting senior engineering pipeline.
Sentiment-score Instantly replies with Claude. Hot leads jump to your inbox; cold ones archived clean.
Read the CV with claude, map who would actually want them via TheirStack + Apollo, name the hiring manager via Prospeo, send the anonymized profile through Instantly. Stops at the reply.
Re-qualify cold Recruit CRM candidates against the current ICP. Warm leads back into active flow.
Every tool ships with a written playbook, and the agent loads it before the first call. Auth, rate limits, what each call costs, which actions need your approval, and the mistakes worth avoiding. It is the difference between an agent that knows the tool and one that guesses at it.
Read the Instantly docs →Routing layer for Instantly v2 API work. WHAT/guardrails here; HOW in reference/endpoints.md
(all 167 ops, generated from the OpenAPI spec) and the Python client lib/instantly.py.
https://api.instantly.ai (all v2 paths under /api/v2/...)Authorization: Bearer <API_KEY> — v2 key from Instantly → Settings → API.INSTANTLY_API_KEY; never hardcode. Client raises if missing.reference/endpoints.md from it when the API changes.list_leads is POST /api/v2/leads/list with a filter body — not a GET.POST /api/v2/leads/add; move is POST /api/v2/leads/move.limit + starting_after; responses carry next_starting_after.
The client's _paginate follows it (cursor in query for GET lists, in body for leads/list).PATCH /…/{id}. Activate/pause are POST /campaigns/{id}/activate|pause.campaigns:create, all:all) — your key must have them.Instantly sends cold email. Treat these as irreversible, approval-gated actions:
activate_campaign starts/resumes sending immediately.add_leads_bulk into an active campaign feeds people into live sending.Typed methods for the recruiting/outreach core: campaigns (list/get/create/patch/delete/
activate/pause), campaign analytics (analytics / overview / daily), leads (create/list/get/patch/
add_bulk/move/update_interest_status), lead-lists (list/create), webhooks (list/create/delete/
event-types). For anything else in the 167-op surface, use the generic request(method, path, …)
passthrough and consult reference/endpoints.md.
Canonical flow: source + enrich leads (your sourcing/enrichment adapters) into a CSV → map rows to
Instantly lead fields → create_lead_list → add_leads_bulk(list_id=…) → review → add_leads_bulk
into a campaign (or move) → activate_campaign only after approval. Never read large CSVs into context.
Field-shape note: these are vendor-native operational notes. The client returns the raw vendor JSON and uses the method names in this file — read field shapes accordingly (no normalized-wrapper /
result.data.prefix).
list_campaigns before any add.get_campaign). Literal UTC is unsupported - map to a supported equivalent; e.g. use America/Detroit where the user asks for America/New_York if the exact value is rejected."0".."6" (sunday..saturday); out-of-range rejected.list_leads filters: accepts campaign/campaign_id (alias), plus list_id, in_campaign, in_list, search; omit all to list globally. (Our adapter: list_leads is POST /api/v2/leads/list.)lib/instantly.pyImport: from lib.instantly import Instantly → instantiate Instantly() (reads key from env). Base: https://api.instantly.ai. Generic passthrough: request(method, path, *, params, json).
activate_campaign(campaign_id: str) -> dict — POST /api/v2/campaigns/{id}/activate — starts/resumes sending (outreach!).add_leads_bulk(leads: list[dict], campaign_id: str | None = None, list_id: str | None = None, **extra) -> dict — POST /api/v2/leads/add — add leads in bulk to a campaign OR list.create_campaign(payload: dict) -> dict — POST /api/v2/campaigns. See reference/endpoints.md + schemas/campaign for body.create_lead(payload: dict) -> dictcreate_lead_list(payload: dict) -> dictcreate_webhook(payload: dict) -> dictdelete_campaign(campaign_id: str) -> dictdelete_webhook(webhook_id: str) -> dictget_campaign(campaign_id: str) -> dictget_campaign_analytics(**params) -> Anyget_campaign_analytics_overview(**params) -> Anyget_daily_campaign_analytics(**params) -> Anyget_lead(lead_id: str) -> dictlist_campaigns(**filters) -> Iterator[dict] — GET /api/v2/campaigns (cursor-paginated).list_lead_lists(**filters) -> Iterator[dict]list_leads(filters: dict | None = None) -> Iterator[dict] — POST /api/v2/leads/list — NOTE: list is a POST with a filter body (cursor-paginated).list_webhook_event_types() -> Anylist_webhooks(**filters) -> Iterator[dict]move_leads(payload: dict) -> dict — POST /api/v2/leads/move — move leads to a campaign or list.patch_campaign(campaign_id: str, payload: dict) -> dictpatch_lead(lead_id: str, payload: dict) -> dictpause_campaign(campaign_id: str) -> dictupdate_lead_interest_status(payload: dict) -> dict