# SendBuzzPro > Email API for apps and AI agents. Sends through Amazon SES with per-key budgets, an approval queue, a kill switch and a full message log. - Base URL: /v1 · Auth: Authorization: Bearer ml_… · MCP: /mcp - Send: POST /v1/emails {from, to, subject, html|text, tags?, scheduled_at?, request_approval?} with Idempotency-Key header - Errors: {"error":{"code","message","fix","docs_url"}}. Read "fix" before retrying. Budget errors (agent_budget_exceeded, plan_*) won't succeed on immediate retry. - Only email people who opted in. Cold email is not allowed. ## Endpoints - POST /v1/emails: Send one email. Body: from, to, cc, bcc, reply_to, subject, html, text, headers, tags, attachments [{filename, content (base64), content_type}], scheduled_at, request_approval, topic, unsubscribe. Header: Idempotency-Key. Returns the email with its status: sent, queued, scheduled, held or suppressed. - POST /v1/emails/batch: Send up to 500 emails. Body: an array of email objects. Returns {data, errors}; each email is accepted or refused on its own. - GET /v1/emails: List emails. Query: status (comma separated), to, key_id, broadcast_id, before, limit (≤100). Returns {data, has_more}. - GET /v1/emails/{id}: Get an email. Status, reason and event timeline. - GET /v1/emails/{id}/raw: Raw message. The exact MIME message (message/rfc822). - POST /v1/emails/{id}/cancel: Cancel. Scheduled, queued or held emails. - POST /v1/emails/{id}/approve: Approve a held email. Console only (owner, admin or approver). Body: note (optional). Who approved it and the note are kept in the timeline and the audit log. - POST /v1/emails/{id}/reject: Reject a held email. Console only (owner, admin or approver). Body: note (the reason). - POST /v1/inboxes: Create an inbox. Body: username (optional; random if empty), domain (your verified domain with the receiving MX record, or empty for the shared domain), name. Use username "*" on your own domain for a catch-all. - GET /v1/inboxes: List inboxes. Addresses and unread conversation counts. - GET /v1/inboxes/{id}/threads: List conversations. Query: unread=true, limit, before. - POST /v1/inboxes/{id}/send: Start a conversation. Same body as /v1/emails; sent from the inbox address so replies come back to it. - GET /v1/threads/{id}: Get a conversation. Every received and sent message, oldest first. - PATCH /v1/threads/{id}: Mark read / unread. Body: {"read": true}. - GET /v1/messages/{id}: Get a received message. Text, HTML, attachments list, SPF/DKIM/DMARC/spam verdicts, is_auto (auto-replies, lists, bounces). - GET /v1/messages/{id}/attachments/{index}: Download an attachment. - POST /v1/messages/{id}/reply: Reply in the conversation. Body: text, html, reply_all, request_approval, attachments. Sets In-Reply-To and References. Same budgets as /v1/emails. - GET /v1/budget: Remaining budget. This key's per-minute/hour/day/month use and limits, and the account plan. - GET /v1/domains: List domains. With DNS records and per-record status. - POST /v1/domains/{id}/check: Check DNS now. - GET /v1/webhooks: List webhooks. - POST /v1/webhooks: Add a webhook. Body: url, events (array, or ["*"]). Returns the signing secret once. - GET /v1/webhooks/deliveries: Delivery log. Query: webhook_id, status (pending | delivered | dead). - POST /v1/webhooks/replay: Replay deliveries. Body: delivery_id, or webhook_id to replay every dead delivery. - POST /v1/contacts: Add or update a contact. Body: email, first_name, last_name, data, topics {topic_id: true|false}. Only people who opted in. - GET /v1/contacts: List contacts. Query: search, topic, limit, offset. - GET /v1/topics: List topics. - GET /v1/broadcasts: List broadcasts. - GET /v1/suppressions: List suppressed addresses. Query: search, reason (bounce | complaint | unsubscribed | manual). - POST /v1/suppressions: Suppress an address. Body: email. - GET /v1/approvals: Recent approval decisions. Console only. Who approved or rejected each email, their role and note. - GET /v1/members: List team members. Console only. Members, roles, pending invites and seats {used, limit}. - POST /v1/members: Invite a member. Console only (owner or admin). Body: email, role (admin | developer | approver | viewer). Emails a one-time invite link, or returns invite_url when the server has no NOTIFY_FROM. - PATCH /v1/members/{id}: Change a role. Console only (owner or admin). Body: role. Applies to the member's sessions right away. - DELETE /v1/members/{id}: Remove a member. Console only (owner or admin). Signs them out everywhere. - POST /v1/members/{id}/link: Resend an invite / sign-in link. Console only (owner or admin). Emailed, or returned as url when the server has no NOTIFY_FROM. - POST /v1/login/email: Email me a sign-in link. No auth. Body: email. Same answer whether or not the address is a member. - POST /v1/login/link: Use a sign-in link. No auth. Body: token. One use, 15 minutes. Sets the session cookie. - POST /v1/invites/accept: Accept an invite. No auth. Body: token, name. One use, 7 days. Sets the session cookie. - GET /v1/audit: Audit log. Console only (owner or admin); API keys have no scopes, so they can't read it. Query: action (e.g. key or key.created), actor, q, since, until (YYYY-MM-DD), before, limit (≤200), format=csv (up to 10,000 rows). ## Error codes - agent_budget_exceeded (429): The API key went over one of its own budgets (minute, hour, day or month). The error says which window, the limit and what is used. Wait for the window to pass, or ask the account owner to raise the budget in the console. A key can’t raise its own budget. - plan_daily_limit (429): The Free plan’s 100 emails a day are used. Wait until midnight UTC or upgrade. - plan_monthly_limit (429): The plan’s monthly emails are used. There are no overage charges. Sending resumes on the 1st (UTC), or upgrade. - lifetime_pool_used (429): All emails in a lifetime plan are used. Buy a larger tier or switch to a monthly plan. - sending_paused (403): The account owner turned on the kill switch. The owner resumes sending on the console Overview page. - key_paused (403): This API key is paused. Resume it under API keys. - account_paused (403): Sending is paused because bounces or spam complaints were too high (4% / 0.08% over 30 days). Remove bad addresses and appeal from the console. A person reviews every appeal. - domain_not_added (422): The from address uses a domain that isn’t on this account. Add it under Domains. - domain_not_verified (422): The domain’s DNS records aren’t verified yet. Add the records under Domains and press Check now. - invalid_from (422): The from address is missing or invalid. Use "Name ". - invalid_recipient (422): One address in to, cc, bcc or reply_to is invalid. The error names the field. Fix or remove it. - missing_recipient (422): There is no “to”. Add at least one address. - too_many_recipients (422): More than 50 recipients in one email. Use /v1/emails/batch to send one email per person. - missing_subject (422): The subject is empty. - subject_too_long (422): The subject is over 998 characters. - missing_content (422): There is no html or text. Send html, text or both. - invalid_header (422): A custom header is reserved, malformed or has a line break. Use the dedicated field (from, to, reply_to…) instead. - invalid_attachment (422): An attachment has no filename or isn’t base64. - message_too_large (422): The email is over 10 MB. Link to large files instead. - invalid_scheduled_at (422): scheduled_at isn’t an ISO 8601 time within 30 days. - invalid_batch (422): The batch body isn’t a JSON array. - batch_too_large (422): More than 500 emails in one batch. Split it. - idempotency_conflict (409): This Idempotency-Key was used for a different request in the last 24 hours. Use a new key for a new email. Retrying the same request with the same key is always safe. - invalid_idempotency_key (400): The Idempotency-Key is over 200 characters. - invalid_api_key (401): The key is missing, malformed or deleted. Send “Authorization: Bearer ml_…”. - unauthorized (401): Not signed in. - console_only (403): Keys, budgets, approvals, the kill switch, the team and the audit log are only for people signed in to the console. - role_forbidden (403): Your role can’t do this (e.g. a viewer creating a key, or a developer approving an email). Ask an owner or admin to change your role under Settings → Team. - seat_limit (403): Your plan’s seats are used (owner and pending invites count). Remove a member or invite, or upgrade. - member_exists (409): That person is already on the team. - member_not_found (404): - owner_protected (403): The owner can’t be changed or removed; ownership follows the license key. - invalid_role (422): Role must be admin, developer, approver or viewer. - invite_invalid (400): The invite link was used, replaced or is over 7 days old. Ask the owner to resend it. - link_invalid (400): The sign-in link was used or is over 15 minutes old. Ask for a new one. - invalid_email (422): Not a valid email address. - bad_origin (403): A cross-site request to the console API was blocked. - not_held (409): The email isn’t waiting for approval. - not_cancelable (409): Only scheduled, queued or held emails can be canceled. - email_not_found (404): No email with that id (old emails are removed after the plan’s history window). - provider_error (502): Amazon SES refused the request. Retry with the same Idempotency-Key. - invalid_domain (400): Not a valid domain name. - domain_taken (409): The domain is on another SendBuzzPro account. Contact support if it is yours. - domain_limit (403): Your plan’s domain limit is reached. - domain_not_found (404): - invalid_webhook_url (400): Webhook URLs must be public https addresses. - invalid_events (400): Unknown webhook event types. - consent_required (422): Imports need confirmation that everyone opted in. - invalid_contact_email (422): - empty_audience (422): Nobody would receive the broadcast. - broadcast_locked (409): Only draft broadcasts can be edited or sent. - complaint_suppression (409): That person marked your email as spam. Only remove it with confirm=true if they asked to be emailed again. - inbox_limit (403): Your plan’s inbox limit is reached. Delete an inbox or upgrade. - address_taken (409): That inbox address is taken. Pick another username. - username_reserved (422): Names like support, admin or billing are reserved on the shared domain. Use a more specific name, or your own domain. - invalid_username (422): Usernames use letters, numbers, dots, dashes and underscores. - no_inbound_domain (400): Receiving isn’t set up for that domain. Use your own domain with the receiving MX record. - catch_all_shared (422): Catch-all inboxes only work on your own domain. - thread_not_found (404): - message_not_found (404): - inbox_not_found (404): - attachment_not_found (404): - invalid_json (400): The body isn’t valid JSON. - not_found (404): Unknown endpoint. - server_error (500): Something failed on our side. Retry with the same Idempotency-Key. ## Webhook events email.queued, email.scheduled, email.held, email.approved, email.rejected, email.canceled, email.sent, email.delivered, email.delivery_delayed, email.bounced, email.soft_bounced, email.complained, email.opened, email.clicked, email.failed, email.suppressed, email.received, contact.created, contact.unsubscribed, domain.verified, domain.failed, account.warned, account.paused, key.budget_exceeded ## Docs - [API reference](/docs) - [OpenAPI](/openapi.json)