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 prospectsprospects:write: Create, update and delete prospectsmessages:read: Read generated emailsmessages:write: Generate and edit emailsmessages:send: Send emails to prospectsmessages:approve: Approve one reviewed draft at a time into the send queue, with confirmcampaigns:read: Read campaigns and cadencescampaigns:write: Create and change campaignssuppressions:read: Read the do-not-contact listsuppressions:write: Add to the do-not-contact listanalytics:read: Read performance statssettings:read: Read workspace settings and setup statussettings:write: Change settings and connect provider keysdomains:read: Check sending domains: DNS records, blocklists, expiry and warm-upworkspace: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 expiredforbidden: key lacks the scope named inrequired_scopeinvalid_request: body failed validation; the message names the fieldnot_found: no such record in this workspacequota_exceeded: plan limit reached;GET /healthfor what is leftconflict: the action contradicts current state, e.g. sending twicerate_limited: back off forRetry-Aftersecondsprovider_error: your AI provider failed; usually worth retryingservice_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.
| Tool | Scope | Also needs |
|---|---|---|
list_prospects | prospects:read | nothing else |
add_prospects | prospects:write | nothing else |
draft_email | messages:write | nothing else |
list_due_follow_ups | prospects:read | nothing else |
compose_draft | messages:write | nothing else |
draft_social_message | messages:write | nothing else |
list_drafts | messages:read | nothing else |
edit_draft | messages:write | nothing else |
send_email | messages:send | nothing else |
schedule_draft | messages:send | nothing else |
approve_draft | messages:approve | nothing else |
list_campaigns | campaigns:read | nothing else |
get_campaign_stats | campaigns:read | nothing else |
create_campaign | campaigns:write | admin |
update_campaign | campaigns:write | admin |
list_campaign_steps | campaigns:read | nothing else |
set_campaign_steps | campaigns:write | admin |
enroll_in_campaign | campaigns:write | admin |
mark_replied | prospects:write | nothing else |
check_suppression | suppressions:read | nothing else |
suppress_address | suppressions:write | admin |
exclude_companies | suppressions:write | admin |
get_performance | analytics:read | nothing else |
list_sdrs | analytics:read | nothing else |
pause_sdr | settings:write | admin |
resume_sdr | messages:send | nothing else |
get_prospect | prospects:read | nothing else |
discard_draft | messages:write | admin |
skip_draft | messages:write | nothing else |
preview_prospects | prospects:write | nothing else |
update_prospect | prospects:write | nothing else |
resume_prospect | prospects:write | nothing else |
remove_prospect | prospects:write | admin |
remove_prospects | prospects:write | admin |
get_autopilot | messages:read | nothing else |
get_setup_status | settings:read | nothing else |
update_settings | settings:write | admin |
enrich_domains | prospects:write | nothing else |
read_website | settings:read | nothing else |
infer_setup | settings:read | admin |
confirm_setup | settings:write | admin |
propose_targets | prospects:read | nothing else |
find_prospects | prospects:write | nothing else |
connect_smtp_mailbox | settings:write | admin |
list_replies | messages:read | nothing else |
mark_reply_handled | messages:write | nothing else |
list_tasks | messages:read | nothing else |
complete_task | messages:write | nothing else |
list_meetings | prospects:read | nothing else |
get_booking_link | prospects:read | nothing else |
list_call_queue | prospects:read | nothing else |
list_targets | prospects:read | nothing else |
update_target | prospects:write | nothing else |
list_sending_domains | settings:read | nothing else |
sending_domain_health | domains:read | nothing else |
add_sending_domain | settings:write | admin |
add_sending_address | settings:write | admin |
buy_sending_domain | settings:write | nothing else |
list_suppressions | suppressions:read | nothing else |
get_usage | settings:read | nothing else |
get_deep_performance | analytics:read | nothing else |
draft_reply | messages:write | nothing else |
send_reply | messages:send | nothing else |
unschedule_draft | messages:write | nothing else |
list_outbox | messages:read | nothing else |
prepare_call | messages:write | nothing else |
log_call | prospects:write | nothing else |
list_excluded_companies | suppressions:read | nothing else |
include_company | suppressions:write | admin |
archive_target | campaigns:write | admin |
update_sender | settings:write | admin |
list_workspaces | settings:read | OAuth, or a key for every workspace |
switch_workspace | settings:read | OAuth, or a key for every workspace |
create_workspace | settings:write | OAuth only |
list_team | workspace:admin | admin |
list_activity | workspace:admin | admin |
invite_member | workspace:admin | admin |
revoke_invite | workspace:admin | admin |
set_member_role | workspace:admin | admin |
remove_member | workspace:admin | admin |
list_api_keys | workspace:admin | admin |
revoke_api_key | workspace:admin | nothing else |
list_connections | workspace:admin | admin |
revoke_connection | workspace:admin | nothing else |
set_autopilot | messages:send | admin |
disconnect_mailbox | settings:write | admin |
publish_domain_records | settings:write | admin |
set_domain_renewal | settings:write | admin |
release_domain_for_transfer | workspace:admin | admin |
publish_mailbox_dns | settings:write | admin |
update_workspace_name | settings:write | admin |
export_workspace | workspace:admin | nothing else |
get_outbound_rules | settings:read | nothing else |
set_outbound_rules | settings:write | nothing else |
confirm_customer_list_loaded | settings:write | admin |
list_account_tags | suppressions:read | nothing else |
tag_account | suppressions:write | admin |
get_workspace_pause | settings:read | nothing else |
resume_workspace_sending | messages:send | nothing else |
list_crm_destinations | settings:read | nothing else |
set_crm_destination | workspace:admin | admin |
test_crm_destination | settings:write | admin |
get_teams_channel | settings:read | nothing else |
set_teams_channel | workspace:admin | admin |
test_teams_channel | settings:write | admin |
get_support_access | settings:read | nothing else |
set_support_access | workspace:admin | admin |
end_support_access | settings:write | nothing else |
refresh_calendar | settings:write | admin |
test_ai_model | settings:write | admin |
find_firm_domain | prospects:write | admin |
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.