Your setter team,
programmable.
Everything Sales Momenta does — leads, conversations, DMs, voice notes in a cloned voice, pipeline, analytics — exposed through a clean REST API and a native MCP server any coding agent can drive.
Overview#
One API key unlocks two surfaces backed by the exact same operations, so behavior never drifts between them:
REST — /v1
Predictable resource endpoints, cursor pagination, typed errors, an OpenAPI 3.1 spec you can import into Postman or generate clients from.
MCP — /mcp
A Model Context Protocol server (Streamable HTTP). Claude Code, Cursor, or any MCP client gets 17 tools with rich descriptions and behaviour annotations — read-only tools marked as such, destructive ones flagged — so agents orient themselves and get to work.
Sandbox included. Keys created in a demo workspace hit the same API against simulated Instagram — sends are safe, and demo leads even write back. Build against the sandbox, then swap the key.
Quickstart#
1. In the app, open Settings → API & MCP → Create key, pick scopes, copy the sm_live_… key (shown once). 2. Say hello:
curl https://api.salesmomenta.com/v1/me \ -H "Authorization: Bearer sm_live_YOUR_KEY"
{
"ok": true,
"name": "Coach Ava",
"instagram": "coachava.fit",
"pipeline_stages": ["New", "Qualified", "Engaged", "Call Booked", "Showed", "Closed", "Lost"],
"counts": { "leads": 28, "conversations": 28, "awaiting_reply": 3 },
"key": { "name": "Production agent", "scopes": ["leads:read", "…"] }
}3. Or skip REST entirely and hand the whole workspace to your coding agent — see the MCP server.
Authentication & scopes#
Every request carries Authorization: Bearer sm_live_…. Keys are hashed at rest, revocable instantly, and scoped — a key can read the pipeline without being able to message anyone. A missing scope returns 403 missing_scope naming exactly what's needed.
| Scope | Grants |
|---|---|
leads:read | Read leads and contact details |
leads:write | Update stage, temperature, pause state |
conversations:read | Read conversations and full transcripts |
messages:send | Send DMs into conversations |
ai:use | Generate AI reply suggestions |
voice:use | List voice profiles, synthesize + send voice notes |
pipeline:read | Stages and live lead counts |
team:read | Roster, allocation, 7-day stats |
analytics:read | Messaging and lead analytics |
webhooks:manage | Create and delete webhook endpoints |
The MCP server#
Sales Momenta speaks the Model Context Protocol natively — protocol 2025-06-18 over Streamable HTTP, stateless, authenticated with the same API keys. Point any MCP client at https://api.salesmomenta.com/mcp:
claude mcp add --transport http momenta https://api.salesmomenta.com/mcp \ --header "Authorization: Bearer sm_live_YOUR_KEY"
{
"mcpServers": {
"momenta": {
"url": "https://api.salesmomenta.com/mcp",
"headers": { "Authorization": "Bearer sm_live_YOUR_KEY" }
}
}
}curl https://api.salesmomenta.com/mcp \
-H "Authorization: Bearer sm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
"params":{"name":"list_conversations","arguments":{"state":"our_move"}}}'Agents orient themselves. The server's initialize response carries instructions: which workspace it is, whether it's a sandbox, what the key can do, and where to start (get_workspace → conversations in our_move). Tool results include structuredContent, so agents get typed data, not prose to parse.
Tool catalog
| Tool | What it does |
|---|---|
get_workspace | Overview: stages, counts, scopes — the agent's first call |
list_leads / get_lead | Search and read prospects (query, stage, temperature) |
update_lead | Move stage, set hot/warm/cold, pause outreach |
list_conversations / get_conversation | Work queues and full transcripts, 24h-window status |
send_message | Send a real DM (window-aware, 1000-char cap) |
generate_reply_suggestions | 3 on-brand drafts from the workspace's trained AI — never auto-sends |
send_voice_note / list_voice_profiles | Synthesize in the coach's cloned voice and send |
get_pipeline / get_team / get_analytics | Stages with counts · roster with stats · messaging metrics |
create_lead | Add a prospect by Instagram handle; returns the existing lead instead of duplicating |
list_webhooks / create_webhook / delete_webhook | Subscribe to events and react, instead of polling |
REST · Workspace#
Name, Instagram handle, timezone, demo flag, pipeline stage names, live counts, and the calling key's scopes. No scope required — any valid key can orient itself.
REST · Leads#
scope leads:read
| Param | Meaning |
|---|---|
query | Matches name, IG username, or email |
stage | Pipeline stage name (case-insensitive) |
temperature | hot · warm · cold |
limit / after | Up to 100 per page; pass next_cursor back as after |
scope leads:write
{ "ig_username": "@maya.fit", "first_name": "Maya", "temperature": "warm" }Safe to call repeatedly: if the handle is already in the workspace you get the existing lead back with already_existed: true instead of a duplicate. Defaults to the first pipeline stage.
scope leads:read
scope leads:write
{ "stage": "Qualified", "temperature": "hot" }Stage changes fire the lead.stage_changed webhook — whether they come from the API or from someone dragging a card in the app.
REST · Conversations & messaging#
scope conversations:read
Filter by state — our_move (lead waiting on you — work these first), their_move, snoozed, done — and needs_human=true for AI escalations.
scope conversations:read · message_limit up to 200 · voice notes include transcripts once transcribed
scope messages:send
{ "text": "Hey! Circling back on this — how did the week of training go?" }Sends ride the same rail as the app: Meta's 24-hour reply window is enforced (a closed window is a clean 422, nothing is sent), demo workspaces simulate delivery, and every send fires the message.sent webhook.
REST · AI suggestions#
scope ai:use
Runs the workspace's trained setter brain — persona, offers, guardrails, learned rules — and returns three angles. It never sends; pair it with the send endpoint.
{ "ok": true, "suggestions": [
{ "text": "okay so what's actually getting in the way...", "angle": "question-first" },
{ "text": "haha fair enough. real talk though...", "angle": "mirror-tone" },
{ "text": "let's just get you on a quick call...", "angle": "push-to-call" }
] }REST · Voice notes#
scope voice:use
scopes voice:use + messages:send
{ "text": "Yo! Just heard your last message — love the energy. Let's lock that call in.",
"voice_profile_id": "optional — defaults to the workspace's default voice" }Synthesizes in the coach's cloned voice (≤600 chars ≈ 45s), stores the audio, and sends it as a real Instagram voice note.
REST · Pipeline · Team · Analytics#
scope pipeline:read
scope team:read · roles, lead-share %, hours, per-member sent/assigned/waiting/booked
scope analytics:read · window 1–90 days · received, sent, AI share, new leads, booked-stage count
Webhooks#
Register an HTTPS endpoint and we POST signed JSON as things happen — including changes made by humans inside the app.
| Event | Fires when |
|---|---|
lead.created | A new conversation/lead lands in the workspace |
message.received | An inbound DM arrives |
message.sent | Any outbound send — human, AI, or API |
lead.stage_changed | A lead moves stage — via API or dragged in the app |
{
"id": "f4a1…", "event": "lead.stage_changed", "created_at": "2026-08-24T02:41:00Z",
"workspace_id": "…",
"data": { "lead_id": "…", "name": "Maya Torres", "from_stage": "Qualified", "to_stage": "Call Booked" }
}Verify the signature
Every delivery carries sm-signature — HMAC-SHA256 of the raw body with your endpoint's secret (shown once at creation) — plus sm-event.
import crypto from 'node:crypto'; function verify(rawBody, header, secret) { const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex'); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(header)); }
Endpoints that fail 20 deliveries in a row are switched off (visible in Settings) instead of hammered forever. 2xx within 5 seconds counts as delivered.
Errors#
{ "ok": false, "error": {
"code": "missing_scope",
"message": "This key doesn't have the \"messages:send\" scope. …",
"docs": "https://salesmomenta.com/developers#errors" } }| HTTP | Code | Meaning |
|---|---|---|
| 401 | unauthorized · key_revoked | Missing, unknown, or revoked key |
| 403 | missing_scope | The key lacks the named scope |
| 404 | not_found | No such resource in this workspace |
| 400 | invalid_stage · invalid_state · too_long · … | The message tells you exactly what to fix |
| 422 | SP-2003 window closed · no_voice · … | Valid request, product rules say no |
| 429 | rate_limited | Includes Retry-After |
| 5xx | internal · ai_unavailable · tts_failed | Our side — safe to retry with backoff |
Rate limits#
120 requests/minute per key (token bucket — short bursts are fine). Every response carries X-RateLimit-Limit and X-RateLimit-Remaining; a 429 includes Retry-After seconds. Instagram-side send caps (per-hour/per-day, configured per workspace) are enforced independently of API rate limits.
OpenAPI & llms.txt#
Machine-readable everything:
OpenAPI 3.1
Import into Postman, Insomnia, or generate typed clients.
api.salesmomenta.com/v1/openapi.jsonllms.txt
The whole API in one plain-text page — paste it into any agent's context and it knows what to do.
api.salesmomenta.com/llms.txtQuestions or a scope you need that isn't here? Tell us from inside the app — the platform grows with what you build.