API

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.

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://your-workspace/api/v1/prospects \
  -H "Authorization: Bearer bk_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://your-workspace/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://your-workspace/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://your-workspace/api/v1/messages \
  -H "Authorization: Bearer $API_KEY"

# 4. Send it (requires messages:send)
curl -X POST https://your-workspace/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; check /analytics
  • 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

Checking budget before a batch

GET /api/v1/analytics returns plan.remaining. An agent should read it before starting a large run rather than discovering a limit halfway through.

{
  "data": {
    "plan": {
      "key": "pro",
      "remaining": { "generations": 2840, "emails": 2903, "prospects": 4915 }
    }
  }
}

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, or a from-address on a domain verified in your own Resend account. MastroSDR checks that when the address is saved and again before the first send.
  • 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 same capabilities are 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 bk_live_… header; the tool list is filtered to the scopes that key actually holds, and every call re-checks the scope server-side.

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. mark_replied is what stops a cadence: nothing detects replies automatically, so an agent watching an inbox should call it as soon as it sees one.

get_performance and check_suppression are the two worth calling before a batch. The first tells you what allowance is left, the second who must never be contacted.

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.