AI agents, CLI and MCP
The API was built to be driven by an agent. Responses are small, typed and self-describing, every operation has a stable id, and the CLI and the MCP server are generated from the same registry as this reference, so an agent reads one contract.
The fastest setup
Give the agent a key as an environment variable, then point it at the spec:
export SALES_ENGINE_API_KEY="se_..."
export SALES_ENGINE_API_URL="https://crm.ashwanth.dev"Read https://crm.ashwanth.dev/api/v1/openapi.json for the contract.
Authenticate with Authorization: Bearer $SALES_ENGINE_API_KEY.
Every operation's x-scope says which key scope it needs.MCP server (Claude Code, Claude Desktop, any MCP client)
The sales-engine package ships a stdio MCP server. Add it to .mcp.json and the agent gets forty curated tools that follow the loop a campaign runs: preview_lead_search, source_lead_list, research_company, create_sequence, create_campaign, approve_campaign_leads, generate_campaign_copy, campaign_launch_checklist, launch_campaign, list_inbox, approve_reply_draft, cost_overview, wait_for_job and the rest. list_operations and call_operation reach every operation when no curated tool fits.
{
"mcpServers": {
"sales-engine": {
"command": "node",
"args": ["packages/sales-engine/bin/mcp.mjs"],
"env": { "SALES_ENGINE_API_KEY": "se_...", "SALES_ENGINE_API_URL": "https://crm.ashwanth.dev" }
}
}
}Settings, Developer API writes this block for you, key filled in, the moment a key is created.
CLI
An operation id such as cost.fixed.list is typed as words: sales-engine cost fixed list. Leftover words fill path parameters in order; --flags fill the query on GET and the JSON body otherwise, and values parse as numbers, booleans, JSON or @file.json. --json prints raw JSON for pipes.
sales-engine auth login --key se_... --url https://crm.ashwanth.dev
sales-engine ops campaigns # every campaign operation
sales-engine campaigns list
sales-engine lead-lists create --name "CROs, UK" --requested-count 25 --params '{"mode":"form","use_icp_defaults":true}'
sales-engine wait <job id>
sales-engine campaigns launch <id> --mode now
sales-engine api GET /inbox/drafts --q status=proposedCredentials resolve as --key and --url, then the environment, then ~/.sales-engine/config.json.
Prompts that work
Pull /api/v1/dashboard and /api/v1/inbox?view=replies.
Tell me what moved this week, what did not, and the one thing to fix first.List /api/v1/inbox/drafts?status=proposed. For each, show the reply it answers
and the draft. I will say approve or reject per draft; approve through
/api/v1/inbox/drafts/{id}/approve.List /api/v1/campaigns. For each active one, read /api/v1/campaigns/{id}/analytics
and flag any with a bounce rate over 3 percent or a reply rate under 2.Give the agent a narrow key
A read key for anything that runs unattended. Add write when it should build lists and campaigns, and keep send for a key you supervise: launch, replies and draft approval touch real inboxes. The scopes are the whole reason a key issued today can never quietly gain a power added tomorrow.
Practical notes
- Filter on the server.
?view=replies&classification=interestedis one small response; pulling everything and filtering in the agent wastes its context. - Error codes are stable, so the agent can branch on
insufficient_scopeand tell you exactly which scope to add. - Long work returns a job; the agent should wait for it rather than re-issue the call.
- Send
x-sales-engine-clientso the Calls view in Settings tells the agent's traffic from yours.