API reference

Everything an agent needs to run outreach

Prospects, drafts, sending, follow-up plans, suppression, and its own setup, over HTTP or MCP.

Designed for autonomous agents: stable error codes, explicit scopes, and a machine-readable spec you can hand straight to a model. Team management, billing and the audit log stay in the dashboard deliberately; an agent should not be able to invite people or change your plan.

Start here

The full description lives at /api/v1/openapi.json. It needs no authentication, so an agent can discover the surface before it holds a key. Base URL for every call is /api/v1. The same thing in plain English for a model to read is at /api/v1/agent-guide, and it is the page to hand over if you would rather not write the integration yourself.

Authentication

Create a key in Settings, API. It is shown once and stored only as a hash, so it cannot be recovered later. Send it as a bearer token.

curl https://mastrosdr.com/api/v1/prospects \
  -H "Authorization: Bearer mastrosdr_live_..."

A key acts in the workspace it was made in. A key made with every workspace ticked can also act in any other workspace its maker belongs to: send X-Workspace-Id with a workspace id, and there it can do only what its maker's role allows. It loses a workspace the moment its maker leaves it. No key can widen its own scope.

Scopes

Grant an agent only what it needs.

  • prospects:read: Read prospects
  • prospects:write: Create, update and delete prospects
  • messages:read: Read generated emails
  • messages:write: Generate and edit emails
  • messages:send: Send emails to prospects
  • messages:approve: Approve one reviewed draft at a time into the send queue, with confirm
  • campaigns:read: Read campaigns and cadences
  • campaigns:write: Create and change campaigns
  • suppressions:read: Read the do-not-contact list
  • suppressions:write: Add to the do-not-contact list
  • analytics:read: Read performance stats
  • settings:read: Read workspace settings and setup status
  • settings:write: Change settings and connect provider keys
  • domains:read: Check sending domains: DNS records, blocklists, expiry and warm-up
  • workspace:admin: Manage the team, API keys, connected assistants, data export, support access and webhooks

messages:send is never implied by messages:write. Sending is irreversible, so an agent can draft freely while a human keeps the send button.

A complete run

Add prospects, draft, review, send.

# 1. Add prospects
curl -X POST https://mastrosdr.com/api/v1/prospects \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prospects":[{"companyName":"Acme Roofing","contactName":"Jane Diaz","email":"jane@acme.com"}]}'

# 2. Draft an email
curl -X POST https://mastrosdr.com/api/v1/messages/generate \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"prospectId":"<id from step 1>"}'

# 3. Read what is waiting for review
curl https://mastrosdr.com/api/v1/messages \
  -H "Authorization: Bearer $API_KEY"

# 4. Send it (requires messages:send)
curl -X POST https://mastrosdr.com/api/v1/messages/<message-id>/send \
  -H "Authorization: Bearer $API_KEY"

Errors

Every failure returns the same shape with a stable code you can branch on, rather than a message you would have to pattern match.

{
  "error": {
    "code": "forbidden",
    "message": "This key is missing the messages:send scope, and a key's scopes cannot be changed. Ask a workspace owner to create a new key with messages:send ticked in Settings, Agents and API, or to connect the assistant through OAuth and tick messages:send on its consent screen.",
    "required_scope": "messages:send",
    "docs": "/docs/api"
  }
}
  • unauthorized: key missing, invalid, revoked or expired
  • forbidden: key lacks the scope named in required_scope
  • invalid_request: body failed validation; the message names the field
  • not_found: no such record in this workspace
  • quota_exceeded: plan limit reached; GET /health for what is left
  • conflict: the action contradicts current state, e.g. sending twice
  • rate_limited: back off for Retry-After seconds
  • provider_error: your AI provider failed; usually worth retrying
  • service_unavailable: this deployment cannot do this at all, so stop and tell a person

Checking budget before a batch

GET /api/v1/health is the one to call before a run. Its budget carries sendsRemainingToday, which is the real bound on sending, and prospectSearchesRemainingThisMonth, the only monthly allowance that can refuse a call. Read includedDraftsRemaining too before a large batch of generations: it is the binding pool for a workspace with no provider key of its own.

{
  "data": {
    "budget": {
      "generationsRemaining": 2840,
      "emailsRemainingThisMonth": 2903,
      "prospectsRemaining": 4915,
      "prospectSearchesRemainingThisMonth": 612,
      "sendsRemainingToday": 38,
      "dailySendLimit": 50,
      "includedDraftsRemaining": 9000,
      "includedDraftsTotal": 10000
    }
  }
}

The whole budget object is eight numbers. Four are monthly, sendsRemainingToday and dailySendLimit are today's, and includedDraftsRemaining and includedDraftsTotal are the pool of drafts a workspace with no key of its own draws on.

GET /api/v1/analytics also returns plan.remaining. Use it for the monthly picture, and do not wait for it to reach zero: generations, emails and prospects are fair-use ceilings that will not refuse anybody. prospectSearches is the fourth and the only one that still will.

Guardrails that always apply

The API is not a way around the safety rules. However you call it, the same checks run:

  • The do-not-contact list is checked immediately before every send.
  • Plan quotas are enforced on generation, sending and prospect count.
  • Every sent email gets an unsubscribe link and your postal address.
  • Sending requires a connected mailbox, and your from-address has to match it. MastroSDR refuses the send otherwise rather than mailing under an address you cannot prove you hold.
  • There is no endpoint to remove someone from the do-not-contact list. An opt-out is permanent as far as any agent is concerned.

MCP

The product is also exposed as Model Context Protocol tools at /api/mcp, so a model can discover them without anyone writing an integration. Authenticate with the same Authorization: Bearer mastrosdr_live_… header; the tool list is filtered to the scopes that key actually holds, and every call re-checks the scope server-side.

It is not the same set as the REST API, and the MCP one is the larger. Some things exist only as tools, including reading replies and the call queue, listing and editing saved targeting, connecting an SMTP mailbox, and managing sending domains. The full agent guide lists them by name. Nothing about the tool list is a weaker contract: the same scopes, the same guards and the same refusals apply.

Tools follow the same separation as the REST surface. draft_email needs messages:write and send_email needs messages:send, which is never implied by it, so you can hand an agent the keyboard without handing it the send button.

For a voice assistant that should approve but never send on its own, grant messages:approve instead of messages:send. It allows one tool, approve_draft, which queues a single reviewed draft for sending hours only when the call carries confirm: true, a reason and an idempotencyKey, and is refused once the mailbox's daily limit is used up. send_email, send_reply and schedule_draft stay out of its tool list.

Call mark_replied the moment you see a reply, and do not wait for a scan. Do not assume a cadence has stopped because nothing has been marked: MastroSDR notices a reply on its own only where that reply comes back to it, which is a MastroSDR sending domain or a mailbox read over IMAP. A reply to a connected Gmail is never noticed, so an agent that only polls this API will follow up to somebody who has already answered.

get_performance and check_suppression are the two worth calling before a batch. The first reports plan.remaining, which carries how many more emails can be written before something refuses, and how many business searches are left this month as plan.remaining.prospectSearches; the second names who must never be contacted. prospectSearches is the one to plan discovery against, because it is the only one of the four that can still refuse a call, and it is also on GET /api/v1/health as prospectSearchesRemainingThisMonth.

MCP tools and scopes

Every tool a key or a connected assistant can call, with the scope it needs. A tool marked admin also needs the key's maker to be an owner or admin in the workspace it acts in. messages:send includes messages:approve, and not the other way round.

ToolScopeAlso needs
list_prospectsprospects:readnothing else
add_prospectsprospects:writenothing else
draft_emailmessages:writenothing else
list_due_follow_upsprospects:readnothing else
compose_draftmessages:writenothing else
draft_social_messagemessages:writenothing else
list_draftsmessages:readnothing else
edit_draftmessages:writenothing else
send_emailmessages:sendnothing else
schedule_draftmessages:sendnothing else
approve_draftmessages:approvenothing else
list_campaignscampaigns:readnothing else
get_campaign_statscampaigns:readnothing else
create_campaigncampaigns:writeadmin
update_campaigncampaigns:writeadmin
list_campaign_stepscampaigns:readnothing else
set_campaign_stepscampaigns:writeadmin
enroll_in_campaigncampaigns:writeadmin
mark_repliedprospects:writenothing else
check_suppressionsuppressions:readnothing else
suppress_addresssuppressions:writeadmin
exclude_companiessuppressions:writeadmin
get_performanceanalytics:readnothing else
list_sdrsanalytics:readnothing else
pause_sdrsettings:writeadmin
resume_sdrmessages:sendnothing else
get_prospectprospects:readnothing else
discard_draftmessages:writeadmin
skip_draftmessages:writenothing else
preview_prospectsprospects:writenothing else
update_prospectprospects:writenothing else
resume_prospectprospects:writenothing else
remove_prospectprospects:writeadmin
remove_prospectsprospects:writeadmin
get_autopilotmessages:readnothing else
get_setup_statussettings:readnothing else
update_settingssettings:writeadmin
enrich_domainsprospects:writenothing else
read_websitesettings:readnothing else
infer_setupsettings:readadmin
confirm_setupsettings:writeadmin
propose_targetsprospects:readnothing else
find_prospectsprospects:writenothing else
connect_smtp_mailboxsettings:writeadmin
list_repliesmessages:readnothing else
mark_reply_handledmessages:writenothing else
list_tasksmessages:readnothing else
complete_taskmessages:writenothing else
list_meetingsprospects:readnothing else
get_booking_linkprospects:readnothing else
list_call_queueprospects:readnothing else
list_targetsprospects:readnothing else
update_targetprospects:writenothing else
list_sending_domainssettings:readnothing else
sending_domain_healthdomains:readnothing else
add_sending_domainsettings:writeadmin
add_sending_addresssettings:writeadmin
buy_sending_domainsettings:writenothing else
list_suppressionssuppressions:readnothing else
get_usagesettings:readnothing else
get_deep_performanceanalytics:readnothing else
draft_replymessages:writenothing else
send_replymessages:sendnothing else
unschedule_draftmessages:writenothing else
list_outboxmessages:readnothing else
prepare_callmessages:writenothing else
log_callprospects:writenothing else
list_excluded_companiessuppressions:readnothing else
include_companysuppressions:writeadmin
archive_targetcampaigns:writeadmin
update_sendersettings:writeadmin
list_workspacessettings:readOAuth, or a key for every workspace
switch_workspacesettings:readOAuth, or a key for every workspace
create_workspacesettings:writeOAuth only
list_teamworkspace:adminadmin
list_activityworkspace:adminadmin
invite_memberworkspace:adminadmin
revoke_inviteworkspace:adminadmin
set_member_roleworkspace:adminadmin
remove_memberworkspace:adminadmin
list_api_keysworkspace:adminadmin
revoke_api_keyworkspace:adminnothing else
list_connectionsworkspace:adminadmin
revoke_connectionworkspace:adminnothing else
set_autopilotmessages:sendadmin
disconnect_mailboxsettings:writeadmin
publish_domain_recordssettings:writeadmin
set_domain_renewalsettings:writeadmin
release_domain_for_transferworkspace:adminadmin
publish_mailbox_dnssettings:writeadmin
update_workspace_namesettings:writeadmin
export_workspaceworkspace:adminnothing else
get_outbound_rulessettings:readnothing else
set_outbound_rulessettings:writenothing else
confirm_customer_list_loadedsettings:writeadmin
list_account_tagssuppressions:readnothing else
tag_accountsuppressions:writeadmin
get_workspace_pausesettings:readnothing else
resume_workspace_sendingmessages:sendnothing else
list_crm_destinationssettings:readnothing else
set_crm_destinationworkspace:adminadmin
test_crm_destinationsettings:writeadmin
get_teams_channelsettings:readnothing else
set_teams_channelworkspace:adminadmin
test_teams_channelsettings:writeadmin
get_support_accesssettings:readnothing else
set_support_accessworkspace:adminadmin
end_support_accesssettings:writenothing else
refresh_calendarsettings:writeadmin
test_ai_modelsettings:writeadmin
find_firm_domainprospects:writeadmin

Rate limits

600 requests per minute per key. A 429 includes Retry-After in seconds. Treat it as a signal to back off rather than to rotate keys.

A 429 on POST /messages/{id}/send can also mean the workspace's daily send cap is reached, in which case Retry-After is the time until it resets at midnight UTC, up to a whole day, not seconds. Always read the header rather than assuming a per-minute window.