API reference
Everything an agent needs to run outreach
Prospects, drafts, sending, campaigns, 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_..."
Every key belongs to one workspace. There is no cross-workspace access and no way for a key to 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 prospectscampaigns: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 keys
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.",
"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.
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.
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}/sendcan 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.