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 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
  • 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

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

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.