API · MCP · Webhooks

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.

Base · https://api.salesmomenta.com MCP · /mcp REST · /v1

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
curl https://api.salesmomenta.com/v1/me \
  -H "Authorization: Bearer sm_live_YOUR_KEY"
response
{
  "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.

ScopeGrants
leads:readRead leads and contact details
leads:writeUpdate stage, temperature, pause state
conversations:readRead conversations and full transcripts
messages:sendSend DMs into conversations
ai:useGenerate AI reply suggestions
voice:useList voice profiles, synthesize + send voice notes
pipeline:readStages and live lead counts
team:readRoster, allocation, 7-day stats
analytics:readMessaging and lead analytics
webhooks:manageCreate 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:

terminal
claude mcp add --transport http momenta https://api.salesmomenta.com/mcp \
  --header "Authorization: Bearer sm_live_YOUR_KEY"
mcp.json
{
  "mcpServers": {
    "momenta": {
      "url": "https://api.salesmomenta.com/mcp",
      "headers": { "Authorization": "Bearer sm_live_YOUR_KEY" }
    }
  }
}
curl · tools/call
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

ToolWhat it does
get_workspaceOverview: stages, counts, scopes — the agent's first call
list_leads / get_leadSearch and read prospects (query, stage, temperature)
update_leadMove stage, set hot/warm/cold, pause outreach
list_conversations / get_conversationWork queues and full transcripts, 24h-window status
send_messageSend a real DM (window-aware, 1000-char cap)
generate_reply_suggestions3 on-brand drafts from the workspace's trained AI — never auto-sends
send_voice_note / list_voice_profilesSynthesize in the coach's cloned voice and send
get_pipeline / get_team / get_analyticsStages with counts · roster with stats · messaging metrics
create_leadAdd a prospect by Instagram handle; returns the existing lead instead of duplicating
list_webhooks / create_webhook / delete_webhookSubscribe to events and react, instead of polling

REST · Workspace#

GET/v1/meWorkspace overview

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#

GET/v1/leadsList / search leads

scope leads:read

ParamMeaning
queryMatches name, IG username, or email
stagePipeline stage name (case-insensitive)
temperaturehot · warm · cold
limit / afterUp to 100 per page; pass next_cursor back as after
POST/v1/leadsCreate a lead

scope leads:write

request
{ "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.

GET/v1/leads/{id}One lead + latest conversation

scope leads:read

PATCH/v1/leads/{id}Update stage · temperature · paused

scope leads:write

request
{ "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#

GET/v1/conversationsWork queues

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.

GET/v1/conversations/{id}Full transcript

scope conversations:read · message_limit up to 200 · voice notes include transcripts once transcribed

POST/v1/conversations/{id}/messagesSend a DM

scope messages:send

request
{ "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#

POST/v1/conversations/{id}/suggestions3 on-brand drafts

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.

response
{ "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#

GET/v1/voice/profilesTrained voices

scope voice:use

POST/v1/conversations/{id}/voiceSpeak & send

scopes voice:use + messages:send

request
{ "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#

GET/v1/pipelineStages + live counts

scope pipeline:read

GET/v1/teamRoster + 7-day stats

scope team:read · roles, lead-share %, hours, per-member sent/assigned/waiting/booked

GET/v1/analytics?days=7Messaging metrics

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.

EventFires when
lead.createdA new conversation/lead lands in the workspace
message.receivedAn inbound DM arrives
message.sentAny outbound send — human, AI, or API
lead.stage_changedA lead moves stage — via API or dragged in the app
delivery payload
{
  "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.

node
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#

every error, same shape
{ "ok": false, "error": {
    "code": "missing_scope",
    "message": "This key doesn't have the \"messages:send\" scope. …",
    "docs": "https://salesmomenta.com/developers#errors" } }
HTTPCodeMeaning
401unauthorized · key_revokedMissing, unknown, or revoked key
403missing_scopeThe key lacks the named scope
404not_foundNo such resource in this workspace
400invalid_stage · invalid_state · too_long · …The message tells you exactly what to fix
422SP-2003 window closed · no_voice · …Valid request, product rules say no
429rate_limitedIncludes Retry-After
5xxinternal · ai_unavailable · tts_failedOur 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.json

llms.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.txt

Questions or a scope you need that isn't here? Tell us from inside the app — the platform grows with what you build.