openapi: 3.0.3 info: title: 'ExoSend API Documentation' description: 'Multi-tenant SMS & Email platform providing reliable message delivery through provider integrations.' version: 1.0.0 servers: - url: 'https://send.exoclass.com' tags: - name: 'SMS Operations' description: "\nAPIs for sending SMS messages and checking delivery status." - name: 'Email Sending' description: "\nAPIs for sending transactional and marketing emails, checking delivery status,\nand listing sent emails. Credits are deducted per email sent." - name: 'Email Domains' description: "\nAPIs for managing email sending domains. Domains must have proper DNS records (SPF, DKIM, DMARC)\nconfigured and verified before they can be used for sending emails." - name: 'Email Templates' description: "\nAPIs for managing reusable email templates with variable substitution.\nTemplates support MJML, HTML, and plain-text content with merge tags for personalization.\nSystem templates are shared across all organizations and cannot be edited by non-admins." - name: 'Email Campaigns' description: "\nAPIs for creating, managing, and sending email campaigns. Campaigns follow a lifecycle:\ndraft -> sending/scheduled -> completed/cancelled. Only draft campaigns can be edited or deleted." - name: Contacts description: "\nAPIs for managing email contacts. Contacts are individual email recipients that can be\norganized into lists for campaign targeting. Supports CRUD operations and CSV import." - name: 'Contact Lists' description: "\nAPIs for managing contact lists. Lists are collections of contacts used for targeting\nemail campaigns. Contacts can belong to multiple lists simultaneously." - name: Analytics description: "\nAPIs for retrieving email analytics and engagement metrics. Includes organization-level\noverviews, per-campaign statistics, link tracking, device breakdowns, and individual\nemail event timelines." - name: 'Suppression List' description: "\nAPIs for managing your email suppression list. Suppressed email addresses will not receive\nany emails from your organization. Addresses can be suppressed manually, or automatically\nvia bounces, complaints, and unsubscribes." - name: 'Account Management' description: "\nAPIs for managing your ExoSend account balance and credits." - name: 'Sender ID Management' description: "\nAPIs for managing sender IDs (sender names that appear on SMS messages).\nSender IDs must be approved by administrators and verified by providers before use." - name: 'SMS Campaigns' description: "\nAPIs for creating, managing, and sending bulk SMS campaigns.\nCampaigns follow a lifecycle: draft -> sending/scheduled -> sent/completed/cancelled." components: securitySchemes: default: type: http scheme: bearer description: 'You can retrieve your API token by logging into the ExoSend admin panel at /admin and navigating to your profile settings. Click Create New Token to generate an API token.' security: - default: [] paths: /api/v1/sms/send: post: summary: 'Send Single SMS' operationId: sendSingleSMS description: "Queue a single SMS message for delivery. The message will be sent asynchronously\nand credits will be deducted immediately." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true data: message_id: 9a7f2e5c-1234-5678-90ab-cdef12345678 status: pending cost: 0.035 segments: 1 balance_remaining: 99.965 message: 'SMS queued for sending' properties: success: type: boolean example: true data: type: object properties: message_id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 status: type: string example: pending cost: type: number example: 0.035 segments: type: integer example: 1 balance_remaining: type: number example: 99.965 message: type: string example: 'SMS queued for sending' 404: description: 'Sender ID Not Found' content: application/json: schema: type: object example: success: false error: code: SENDER_NOT_FOUND message: 'The specified sender ID was not found in your organization' details: sender_id: NONEXISTENT properties: success: type: boolean example: false error: type: object properties: code: type: string example: SENDER_NOT_FOUND message: type: string example: 'The specified sender ID was not found in your organization' details: type: object properties: sender_id: type: string example: NONEXISTENT 422: description: '' content: application/json: schema: oneOf: - description: 'Insufficient Credits' type: object example: success: false error: code: INSUFFICIENT_CREDITS message: 'Your account does not have enough credits' details: required: 0.035 available: 0.0 properties: success: type: boolean example: false error: type: object properties: code: type: string example: INSUFFICIENT_CREDITS message: type: string example: 'Your account does not have enough credits' details: type: object properties: required: type: number example: 0.035 available: type: number example: 0.0 - description: 'Invalid Phone Number' type: object example: success: false error: code: INVALID_PHONE_NUMBER message: 'The recipient phone number is invalid' details: recipient: '1234567890' properties: success: type: boolean example: false error: type: object properties: code: type: string example: INVALID_PHONE_NUMBER message: type: string example: 'The recipient phone number is invalid' details: type: object properties: recipient: type: string example: '1234567890' - description: 'Sender ID Not Verified' type: object example: success: false error: code: SENDER_NOT_VERIFIED message: 'The sender ID must be verified before sending messages' details: sender_id: EXOCLASS status: pending status_label: 'Pending Review' properties: success: type: boolean example: false error: type: object properties: code: type: string example: SENDER_NOT_VERIFIED message: type: string example: 'The sender ID must be verified before sending messages' details: type: object properties: sender_id: type: string example: EXOCLASS status: type: string example: pending status_label: type: string example: 'Pending Review' tags: - 'SMS Operations' requestBody: required: true content: application/json: schema: type: object properties: recipient: type: string description: 'Phone number in E.164 format (e.g., +37061234567).' example: '+37061234567' message: type: string description: 'Message content. Maximum 1600 characters for concatenated messages.' example: 'Hello from ExoSend! Your verification code is 123456.' sender_id: type: string description: 'UUID or sender ID string of a verified sender ID from your organization. Can be either a UUID (e.g., "9a7f2e5c-1234-5678-90ab-cdef12345678") or the sender ID name (e.g., "EXOCLASS").' example: EXOCLASS required: - recipient - message - sender_id /api/v1/sms/send-bulk: post: summary: 'Send Bulk SMS' operationId: sendBulkSMS description: "Queue multiple SMS messages to different recipients with the same content.\nInvalid phone numbers will be filtered out, and only valid recipients will receive messages.\nCredits are deducted for all valid recipients before sending." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true data: total_messages: 3 total_cost: 0.105 balance_remaining: 99.895 message_ids: - 9a7f2e5c-1234-5678-90ab-cdef12345678 - 9a7f2e5c-1234-5678-90ab-cdef12345679 - 9a7f2e5c-1234-5678-90ab-cdef12345680 invalid_recipients: [] message: 'Bulk SMS queued for sending' properties: success: type: boolean example: true data: type: object properties: total_messages: type: integer example: 3 total_cost: type: number example: 0.105 balance_remaining: type: number example: 99.895 message_ids: type: array example: - 9a7f2e5c-1234-5678-90ab-cdef12345678 - 9a7f2e5c-1234-5678-90ab-cdef12345679 - 9a7f2e5c-1234-5678-90ab-cdef12345680 items: type: string invalid_recipients: type: array example: [] message: type: string example: 'Bulk SMS queued for sending' 422: description: '' content: application/json: schema: oneOf: - description: 'Some Invalid Recipients' type: object example: success: true data: total_messages: 2 total_cost: 0.07 balance_remaining: 99.93 message_ids: - 9a7f2e5c-1234-5678-90ab-cdef12345678 - 9a7f2e5c-1234-5678-90ab-cdef12345679 invalid_recipients: - invalid-number message: 'Bulk SMS queued for sending' properties: success: type: boolean example: true data: type: object properties: total_messages: type: integer example: 2 total_cost: type: number example: 0.07 balance_remaining: type: number example: 99.93 message_ids: type: array example: - 9a7f2e5c-1234-5678-90ab-cdef12345678 - 9a7f2e5c-1234-5678-90ab-cdef12345679 items: type: string invalid_recipients: type: array example: - invalid-number items: type: string message: type: string example: 'Bulk SMS queued for sending' - description: 'All Recipients Invalid' type: object example: success: false error: code: NO_VALID_RECIPIENTS message: 'No valid recipients found' details: invalid_recipients: - invalid1 - invalid2 properties: success: type: boolean example: false error: type: object properties: code: type: string example: NO_VALID_RECIPIENTS message: type: string example: 'No valid recipients found' details: type: object properties: invalid_recipients: type: array example: - invalid1 - invalid2 items: type: string - description: 'Insufficient Credits' type: object example: success: false error: code: INSUFFICIENT_CREDITS message: 'Your account does not have enough credits' details: required: 0.105 available: 0.05 valid_recipients_count: 3 properties: success: type: boolean example: false error: type: object properties: code: type: string example: INSUFFICIENT_CREDITS message: type: string example: 'Your account does not have enough credits' details: type: object properties: required: type: number example: 0.105 available: type: number example: 0.05 valid_recipients_count: type: integer example: 3 tags: - 'SMS Operations' requestBody: required: true content: application/json: schema: type: object properties: recipients: type: array description: 'Array of phone numbers in E.164 format.' example: - '+37061234567' - '+37061234568' - '+37061234569' items: type: string message: type: string description: 'Message content. Same message sent to all recipients.' example: 'Bulk notification: System maintenance scheduled for tonight.' sender_id: type: string description: 'UUID or sender ID string of a verified sender ID from your organization. Can be either a UUID or the sender ID name.' example: EXOCLASS required: - recipients - message - sender_id '/api/v1/sms/{id}/status': get: summary: 'Get Message Status' operationId: getMessageStatus description: "Retrieve the current delivery status of a previously sent SMS message.\nOnly messages belonging to your organization can be accessed." parameters: [] responses: 200: description: '' content: application/json: schema: oneOf: - description: 'Delivered Message' type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 status: delivered recipient: '+37061234567' cost: 0.035 segments: 1 sent_at: '2026-01-03T14:30:00+00:00' delivered_at: '2026-01-03T14:30:15+00:00' error_message: null error_code: null properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 status: type: string example: delivered recipient: type: string example: '+37061234567' cost: type: number example: 0.035 segments: type: integer example: 1 sent_at: type: string example: '2026-01-03T14:30:00+00:00' delivered_at: type: string example: '2026-01-03T14:30:15+00:00' error_message: type: string example: null error_code: type: string example: null - description: 'Failed Message' type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 status: failed recipient: '+37061234567' cost: 0.035 segments: 1 sent_at: null delivered_at: null error_message: 'Invalid recipient' error_code: INVALID_RECIPIENT properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 status: type: string example: failed recipient: type: string example: '+37061234567' cost: type: number example: 0.035 segments: type: integer example: 1 sent_at: type: string example: null delivered_at: type: string example: null error_message: type: string example: 'Invalid recipient' error_code: type: string example: INVALID_RECIPIENT 404: description: 'Message Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Message not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Message not found' tags: - 'SMS Operations' parameters: - in: path name: id description: 'The UUID of the message.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string /api/v1/emails/send: post: summary: 'Send Single Email' operationId: sendSingleEmail description: "Queue a single email for delivery. The email will be sent asynchronously via the\nconfigured email provider. Credits are deducted immediately. If html_content is provided\nwithout text_content, a plain-text version is auto-generated." parameters: [] responses: 202: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 status: pending to_email: john@example.com subject: 'Welcome to ExoSend!' cost: 0.01 created_at: '2026-01-15T10:30:00+00:00' message: 'Email queued for sending.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 status: type: string example: pending to_email: type: string example: john@example.com subject: type: string example: 'Welcome to ExoSend!' cost: type: number example: 0.01 created_at: type: string example: '2026-01-15T10:30:00+00:00' message: type: string example: 'Email queued for sending.' 422: description: '' content: application/json: schema: oneOf: - description: 'Insufficient Credits' type: object example: success: false error: code: INSUFFICIENT_CREDITS message: 'Your account does not have enough credits' details: required: 0.01 available: 0.0 properties: success: type: boolean example: false error: type: object properties: code: type: string example: INSUFFICIENT_CREDITS message: type: string example: 'Your account does not have enough credits' details: type: object properties: required: type: number example: 0.01 available: type: number example: 0.0 - description: 'Domain Not Verified' type: object example: success: false error: code: DOMAIN_NOT_VERIFIED message: 'The sending domain is registered but not yet verified' details: domain: exosend.com status: pending properties: success: type: boolean example: false error: type: object properties: code: type: string example: DOMAIN_NOT_VERIFIED message: type: string example: 'The sending domain is registered but not yet verified' details: type: object properties: domain: type: string example: exosend.com status: type: string example: pending - description: 'Validation Error' type: object example: success: false error: code: VALIDATION_ERROR message: 'The given data was invalid' details: to_email: - 'Recipient email must be a valid email address' subject: - 'Email subject is required' properties: success: type: boolean example: false error: type: object properties: code: type: string example: VALIDATION_ERROR message: type: string example: 'The given data was invalid' details: type: object properties: to_email: type: array example: - 'Recipient email must be a valid email address' items: type: string subject: type: array example: - 'Email subject is required' items: type: string tags: - 'Email Sending' requestBody: required: true content: multipart/form-data: schema: type: object properties: from_email: type: string description: 'Sender email address. Must match a verified domain if one is registered.' example: hello@exosend.com from_name: type: string description: 'optional Sender display name.' example: 'ExoSend Team' nullable: true to_email: type: string description: 'Recipient email address.' example: john@example.com to_name: type: string description: 'optional Recipient display name.' example: 'John Doe' nullable: true subject: type: string description: 'Email subject line. Maximum 255 characters.' example: 'Welcome to ExoSend!' html_content: type: string description: '(if no text_content) HTML body of the email.' example: '

Welcome!

Thanks for signing up.

' nullable: true text_content: type: string description: '(if no html_content) Plain-text body of the email.' example: 'Welcome! Thanks for signing up.' nullable: true reply_to: type: string description: 'optional Reply-to email address.' example: support@exosend.com nullable: true template_id: type: string description: 'optional UUID of an email template to use.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 nullable: true variables: type: object description: 'optional Template variables for merge tags.' example: first_name: John company: Acme properties: { } nullable: true metadata: type: object description: 'optional Custom key-value metadata to attach to the email.' example: order_id: '12345' campaign: onboarding properties: { } nullable: true email_domain_id: type: string description: 'optional UUID of a specific email domain to use.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 nullable: true attachments: type: array description: 'Must be a file. Must not be greater than 102400 kilobytes.' items: type: string format: binary required: - from_email - to_email - subject - html_content - text_content /api/v1/emails/send-batch: post: summary: 'Send Batch Emails' operationId: sendBatchEmails description: "Queue multiple emails for delivery in a single request. Each email can have different\nrecipients, subjects, and content. All emails are validated and credits are deducted\nfor the entire batch before sending begins. Maximum 1000 emails per batch." parameters: [] responses: 202: description: '' content: application/json: schema: type: object example: success: true data: total_emails: 3 total_cost: 0.03 balance_remaining: 99.97 email_ids: - 9a7f2e5c-1234-5678-90ab-cdef12345678 - 9a7f2e5c-1234-5678-90ab-cdef12345679 - 9a7f2e5c-1234-5678-90ab-cdef12345680 message: 'Batch emails queued for sending.' properties: success: type: boolean example: true data: type: object properties: total_emails: type: integer example: 3 total_cost: type: number example: 0.03 balance_remaining: type: number example: 99.97 email_ids: type: array example: - 9a7f2e5c-1234-5678-90ab-cdef12345678 - 9a7f2e5c-1234-5678-90ab-cdef12345679 - 9a7f2e5c-1234-5678-90ab-cdef12345680 items: type: string message: type: string example: 'Batch emails queued for sending.' 422: description: '' content: application/json: schema: oneOf: - description: 'Insufficient Credits' type: object example: success: false error: code: INSUFFICIENT_CREDITS message: 'Your account does not have enough credits' details: required: 0.03 available: 0.01 email_count: 3 properties: success: type: boolean example: false error: type: object properties: code: type: string example: INSUFFICIENT_CREDITS message: type: string example: 'Your account does not have enough credits' details: type: object properties: required: type: number example: 0.03 available: type: number example: 0.01 email_count: type: integer example: 3 - description: 'Domain Not Verified' type: object example: success: false error: code: DOMAIN_NOT_VERIFIED message: "The sending domain 'exosend.com' is registered but not yet verified" details: domain: exosend.com status: pending properties: success: type: boolean example: false error: type: object properties: code: type: string example: DOMAIN_NOT_VERIFIED message: type: string example: "The sending domain 'exosend.com' is registered but not yet verified" details: type: object properties: domain: type: string example: exosend.com status: type: string example: pending - description: 'Validation Error' type: object example: success: false error: code: VALIDATION_ERROR message: 'The given data was invalid' details: emails: - 'At least one email is required' properties: success: type: boolean example: false error: type: object properties: code: type: string example: VALIDATION_ERROR message: type: string example: 'The given data was invalid' details: type: object properties: emails: type: array example: - 'At least one email is required' items: type: string tags: - 'Email Sending' requestBody: required: true content: multipart/form-data: schema: type: object properties: emails: type: array description: 'Array of email objects to send (1-1000 items).' example: - architecto items: type: string attachments: type: array description: 'Must be a file. Must not be greater than 102400 kilobytes.' items: type: string format: binary required: - emails '/api/v1/emails/batch/{batchId}': get: summary: 'Get Batch Details' operationId: getBatchDetails description: "Retrieve the summary and paginated email list for a previously created batch.\nOnly batches belonging to your organization can be accessed." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: batch: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 subject: 'Welcome to ExoSend!' from_email: hello@exosend.com from_name: 'ExoSend Team' source: api status: pending total_recipients: 3 total_cost: 0.03 total_sent: 0 total_delivered: 0 total_opened: 0 total_bounced: 0 total_failed: 0 delivery_rate: 0.0 open_rate: 0.0 scheduled_at: null created_at: '2026-01-15T10:30:00+00:00' emails: { } properties: success: type: boolean example: true data: type: object properties: batch: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 subject: type: string example: 'Welcome to ExoSend!' from_email: type: string example: hello@exosend.com from_name: type: string example: 'ExoSend Team' source: type: string example: api status: type: string example: pending total_recipients: type: integer example: 3 total_cost: type: number example: 0.03 total_sent: type: integer example: 0 total_delivered: type: integer example: 0 total_opened: type: integer example: 0 total_bounced: type: integer example: 0 total_failed: type: integer example: 0 delivery_rate: type: number example: 0.0 open_rate: type: number example: 0.0 scheduled_at: type: string example: null created_at: type: string example: '2026-01-15T10:30:00+00:00' emails: type: object properties: { } 404: description: 'Batch Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Batch not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Batch not found' tags: - 'Email Sending' parameters: - in: path name: batchId description: 'The UUID of the email batch.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/emails/{id}/status': get: summary: 'Get Email Status' operationId: getEmailStatus description: "Retrieve the current delivery status and tracking events for a previously sent email.\nOnly emails belonging to your organization can be accessed." parameters: [] responses: 200: description: '' content: application/json: schema: oneOf: - description: 'Delivered Email' type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 status: delivered to_email: john@example.com subject: 'Welcome to ExoSend!' cost: 0.01 sent_at: '2026-01-15T10:30:00+00:00' delivered_at: '2026-01-15T10:30:05+00:00' opened_at: '2026-01-15T11:00:00+00:00' clicked_at: '2026-01-15T11:05:30+00:00' bounced_at: null error_message: null error_code: null properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 status: type: string example: delivered to_email: type: string example: john@example.com subject: type: string example: 'Welcome to ExoSend!' cost: type: number example: 0.01 sent_at: type: string example: '2026-01-15T10:30:00+00:00' delivered_at: type: string example: '2026-01-15T10:30:05+00:00' opened_at: type: string example: '2026-01-15T11:00:00+00:00' clicked_at: type: string example: '2026-01-15T11:05:30+00:00' bounced_at: type: string example: null error_message: type: string example: null error_code: type: string example: null - description: 'Bounced Email' type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 status: bounced to_email: invalid@example.com subject: 'Welcome to ExoSend!' cost: 0.01 sent_at: '2026-01-15T10:30:00+00:00' delivered_at: null opened_at: null clicked_at: null bounced_at: '2026-01-15T10:30:10+00:00' error_message: 'Mailbox not found' error_code: '550' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 status: type: string example: bounced to_email: type: string example: invalid@example.com subject: type: string example: 'Welcome to ExoSend!' cost: type: number example: 0.01 sent_at: type: string example: '2026-01-15T10:30:00+00:00' delivered_at: type: string example: null opened_at: type: string example: null clicked_at: type: string example: null bounced_at: type: string example: '2026-01-15T10:30:10+00:00' error_message: type: string example: 'Mailbox not found' error_code: type: string example: '550' 404: description: 'Email Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Email not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Email not found' tags: - 'Email Sending' parameters: - in: path name: id description: 'The UUID of the email.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string /api/v1/emails: get: summary: 'List Sent Emails' operationId: listSentEmails description: "Retrieve a paginated list of all sent emails for your organization, sorted by newest first.\nOptionally filter by delivery status." parameters: - in: query name: status description: 'Filter by email status. Options: pending, sent, delivered, opened, clicked, bounced, failed, complained.' example: delivered required: false schema: type: string description: 'Filter by email status. Options: pending, sent, delivered, opened, clicked, bounced, failed, complained.' example: delivered - in: query name: per_page description: 'Number of results per page (default 25).' example: 10 required: false schema: type: integer description: 'Number of results per page (default 25).' example: 10 responses: 200: description: '' content: application/json: schema: type: object example: success: true data: emails: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 from_email: hello@exosend.com to_email: john@example.com subject: 'Welcome to ExoSend!' status: delivered cost: 0.01 created_at: '2026-01-15T10:30:00+00:00' pagination: total: 42 per_page: 25 current_page: 1 last_page: 2 properties: success: type: boolean example: true data: type: object properties: emails: type: array example: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 from_email: hello@exosend.com to_email: john@example.com subject: 'Welcome to ExoSend!' status: delivered cost: 0.01 created_at: '2026-01-15T10:30:00+00:00' items: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 from_email: type: string example: hello@exosend.com to_email: type: string example: john@example.com subject: type: string example: 'Welcome to ExoSend!' status: type: string example: delivered cost: type: number example: 0.01 created_at: type: string example: '2026-01-15T10:30:00+00:00' pagination: type: object properties: total: type: integer example: 42 per_page: type: integer example: 25 current_page: type: integer example: 1 last_page: type: integer example: 2 tags: - 'Email Sending' /api/v1/email-domains: get: summary: 'List Domains' operationId: listDomains description: "Retrieve all email domains registered for your organization, including their\nverification status and DNS records." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: exosend.com from_email_default: hello@exosend.com from_name_default: ExoSend status: verified spf_verified: true dkim_verified: true dmarc_verified: true is_default: true dns_records: - type: TXT name: exosend.com value: 'v=spf1 include:mailgun.org ~all' verified_at: '2026-01-10T12:00:00+00:00' created_at: '2026-01-10T10:00:00+00:00' properties: success: type: boolean example: true data: type: array example: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: exosend.com from_email_default: hello@exosend.com from_name_default: ExoSend status: verified spf_verified: true dkim_verified: true dmarc_verified: true is_default: true dns_records: - type: TXT name: exosend.com value: 'v=spf1 include:mailgun.org ~all' verified_at: '2026-01-10T12:00:00+00:00' created_at: '2026-01-10T10:00:00+00:00' items: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: type: string example: exosend.com from_email_default: type: string example: hello@exosend.com from_name_default: type: string example: ExoSend status: type: string example: verified spf_verified: type: boolean example: true dkim_verified: type: boolean example: true dmarc_verified: type: boolean example: true is_default: type: boolean example: true dns_records: type: array example: - type: TXT name: exosend.com value: 'v=spf1 include:mailgun.org ~all' items: type: object properties: type: type: string example: TXT name: type: string example: exosend.com value: type: string example: 'v=spf1 include:mailgun.org ~all' verified_at: type: string example: '2026-01-10T12:00:00+00:00' created_at: type: string example: '2026-01-10T10:00:00+00:00' tags: - 'Email Domains' post: summary: 'Add Domain' operationId: addDomain description: "Register a new email sending domain for your organization. After adding, you must\nconfigure the returned DNS records with your domain registrar, then trigger verification.\nDomain registration with the email provider is done asynchronously." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: exosend.com from_email_default: hello@exosend.com from_name_default: 'ExoSend Team' status: pending spf_verified: false dkim_verified: false dmarc_verified: false is_default: false dns_records: - type: TXT name: exosend.com value: 'v=spf1 include:mailgun.org ~all' - type: TXT name: k1._domainkey.exosend.com value: 'k=rsa; p=MIGf...' - type: TXT name: _dmarc.exosend.com value: 'v=DMARC1; p=none;' verified_at: null created_at: '2026-01-15T10:00:00+00:00' message: 'Domain added. Please add the DNS records shown below, then verify.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: type: string example: exosend.com from_email_default: type: string example: hello@exosend.com from_name_default: type: string example: 'ExoSend Team' status: type: string example: pending spf_verified: type: boolean example: false dkim_verified: type: boolean example: false dmarc_verified: type: boolean example: false is_default: type: boolean example: false dns_records: type: array example: - type: TXT name: exosend.com value: 'v=spf1 include:mailgun.org ~all' - type: TXT name: k1._domainkey.exosend.com value: 'k=rsa; p=MIGf...' - type: TXT name: _dmarc.exosend.com value: 'v=DMARC1; p=none;' items: type: object properties: type: type: string example: TXT name: type: string example: exosend.com value: type: string example: 'v=spf1 include:mailgun.org ~all' verified_at: type: string example: null created_at: type: string example: '2026-01-15T10:00:00+00:00' message: type: string example: 'Domain added. Please add the DNS records shown below, then verify.' 422: description: '' content: application/json: schema: oneOf: - description: 'Domain Already Exists' type: object example: success: false error: code: DOMAIN_EXISTS message: 'This domain is already registered for your organization' details: domain: exosend.com properties: success: type: boolean example: false error: type: object properties: code: type: string example: DOMAIN_EXISTS message: type: string example: 'This domain is already registered for your organization' details: type: object properties: domain: type: string example: exosend.com - description: 'Validation Error' type: object example: success: false error: code: VALIDATION_ERROR message: 'The given data was invalid' details: domain: - 'The domain field is required.' properties: success: type: boolean example: false error: type: object properties: code: type: string example: VALIDATION_ERROR message: type: string example: 'The given data was invalid' details: type: object properties: domain: type: array example: - 'The domain field is required.' items: type: string tags: - 'Email Domains' requestBody: required: true content: application/json: schema: type: object properties: domain: type: string description: 'The domain name to register (e.g., "exosend.com").' example: exosend.com from_email_default: type: string description: 'optional Default sender email for this domain.' example: hello@exosend.com nullable: true from_name_default: type: string description: 'optional Default sender display name for this domain.' example: 'ExoSend Team' nullable: true region: type: string description: 'optional Mailgun region: "us" or "eu". When provided, the domain is assigned to the matching Mailgun provider.' example: eu nullable: true required: - domain '/api/v1/email-domains/{id}': get: summary: 'Get Domain Details' operationId: getDomainDetails description: "Retrieve full details of a specific email domain, including DNS records\nand verification status for SPF, DKIM, and DMARC." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: exosend.com from_email_default: hello@exosend.com from_name_default: 'ExoSend Team' status: verified spf_verified: true dkim_verified: true dmarc_verified: true is_default: true dns_records: - type: TXT name: exosend.com value: 'v=spf1 include:mailgun.org ~all' verified_at: '2026-01-10T12:00:00+00:00' created_at: '2026-01-10T10:00:00+00:00' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: type: string example: exosend.com from_email_default: type: string example: hello@exosend.com from_name_default: type: string example: 'ExoSend Team' status: type: string example: verified spf_verified: type: boolean example: true dkim_verified: type: boolean example: true dmarc_verified: type: boolean example: true is_default: type: boolean example: true dns_records: type: array example: - type: TXT name: exosend.com value: 'v=spf1 include:mailgun.org ~all' items: type: object properties: type: type: string example: TXT name: type: string example: exosend.com value: type: string example: 'v=spf1 include:mailgun.org ~all' verified_at: type: string example: '2026-01-10T12:00:00+00:00' created_at: type: string example: '2026-01-10T10:00:00+00:00' 404: description: 'Domain Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Domain not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Domain not found' tags: - 'Email Domains' delete: summary: 'Delete Domain' operationId: deleteDomain description: "Remove an email domain from your organization. This does not affect emails\nthat have already been sent through this domain." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: null message: 'Domain removed.' properties: success: type: boolean example: true data: type: string example: null message: type: string example: 'Domain removed.' 404: description: 'Domain Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Domain not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Domain not found' tags: - 'Email Domains' parameters: - in: path name: id description: 'The UUID of the domain.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/email-domains/{id}/verify': post: summary: 'Verify Domain DNS' operationId: verifyDomainDNS description: "Trigger DNS verification for a domain. This queues a background job that checks\nwhether the required SPF, DKIM, and DMARC records are properly configured.\nThe domain status will be updated asynchronously after verification completes." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: exosend.com from_email_default: hello@exosend.com from_name_default: 'ExoSend Team' status: pending spf_verified: false dkim_verified: false dmarc_verified: false is_default: false dns_records: [] verified_at: null created_at: '2026-01-15T10:00:00+00:00' message: 'DNS verification has been queued.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: type: string example: exosend.com from_email_default: type: string example: hello@exosend.com from_name_default: type: string example: 'ExoSend Team' status: type: string example: pending spf_verified: type: boolean example: false dkim_verified: type: boolean example: false dmarc_verified: type: boolean example: false is_default: type: boolean example: false dns_records: type: array example: [] verified_at: type: string example: null created_at: type: string example: '2026-01-15T10:00:00+00:00' message: type: string example: 'DNS verification has been queued.' 404: description: 'Domain Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Domain not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Domain not found' tags: - 'Email Domains' parameters: - in: path name: id description: 'The UUID of the domain.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/email-domains/{id}/default': patch: summary: 'Set Default Domain' operationId: setDefaultDomain description: "Set a verified domain as the default sending domain for your organization.\nOnly verified domains can be set as default. The previous default domain\nwill be automatically unset." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: exosend.com from_email_default: hello@exosend.com from_name_default: 'ExoSend Team' status: verified spf_verified: true dkim_verified: true dmarc_verified: true is_default: true dns_records: [] verified_at: '2026-01-10T12:00:00+00:00' created_at: '2026-01-10T10:00:00+00:00' message: 'Domain set as default.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 domain: type: string example: exosend.com from_email_default: type: string example: hello@exosend.com from_name_default: type: string example: 'ExoSend Team' status: type: string example: verified spf_verified: type: boolean example: true dkim_verified: type: boolean example: true dmarc_verified: type: boolean example: true is_default: type: boolean example: true dns_records: type: array example: [] verified_at: type: string example: '2026-01-10T12:00:00+00:00' created_at: type: string example: '2026-01-10T10:00:00+00:00' message: type: string example: 'Domain set as default.' 404: description: 'Domain Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Domain not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Domain not found' 422: description: 'Domain Not Verified' content: application/json: schema: type: object example: success: false error: code: DOMAIN_NOT_VERIFIED message: 'Only verified domains can be set as default' properties: success: type: boolean example: false error: type: object properties: code: type: string example: DOMAIN_NOT_VERIFIED message: type: string example: 'Only verified domains can be set as default' tags: - 'Email Domains' parameters: - in: path name: id description: 'The UUID of the domain.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string /api/v1/email-templates: get: summary: 'List Templates' operationId: listTemplates description: "Retrieve all active email templates for your organization, including system templates.\nOptionally filter by category. Returns summary data; use the show endpoint for full content." parameters: - in: query name: category description: 'Filter by template category. Options: transactional, marketing, notification, onboarding, custom.' example: marketing required: false schema: type: string description: 'Filter by template category. Options: transactional, marketing, notification, onboarding, custom.' example: marketing responses: 200: description: '' content: application/json: schema: type: object example: success: true data: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'Welcome Email' slug: welcome-email category: onboarding subject_template: 'Welcome, @{{first_name}}!' is_system: false is_active: true version: 1 times_used: 150 created_at: '2026-01-10T10:00:00+00:00' updated_at: '2026-01-12T14:30:00+00:00' properties: success: type: boolean example: true data: type: array example: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'Welcome Email' slug: welcome-email category: onboarding subject_template: 'Welcome, @{{first_name}}!' is_system: false is_active: true version: 1 times_used: 150 created_at: '2026-01-10T10:00:00+00:00' updated_at: '2026-01-12T14:30:00+00:00' items: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'Welcome Email' slug: type: string example: welcome-email category: type: string example: onboarding subject_template: type: string example: 'Welcome, @{{first_name}}!' is_system: type: boolean example: false is_active: type: boolean example: true version: type: integer example: 1 times_used: type: integer example: 150 created_at: type: string example: '2026-01-10T10:00:00+00:00' updated_at: type: string example: '2026-01-12T14:30:00+00:00' tags: - 'Email Templates' post: summary: 'Create Template' operationId: createTemplate description: "Create a new email template for your organization. Templates can include\nmerge tag variables (e.g., `@{{first_name}}`) that are replaced at send time.\nIf only html_content is provided, a plain-text version is auto-generated." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'Monthly Newsletter' slug: monthly-newsletter category: marketing subject_template: 'Your @{{month}} Newsletter' is_system: false is_active: true version: 1 times_used: 0 created_at: '2026-01-15T10:00:00+00:00' updated_at: '2026-01-15T10:00:00+00:00' description: 'Sent monthly with latest updates' html_content: '

Hello @{{first_name}}

Here is your newsletter.

' text_content: 'Hello @{{first_name}}, here is your newsletter.' mjml_content: null variables: - key: first_name label: 'First Name' default: there required: true message: 'Template created.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'Monthly Newsletter' slug: type: string example: monthly-newsletter category: type: string example: marketing subject_template: type: string example: 'Your @{{month}} Newsletter' is_system: type: boolean example: false is_active: type: boolean example: true version: type: integer example: 1 times_used: type: integer example: 0 created_at: type: string example: '2026-01-15T10:00:00+00:00' updated_at: type: string example: '2026-01-15T10:00:00+00:00' description: type: string example: 'Sent monthly with latest updates' html_content: type: string example: '

Hello @{{first_name}}

Here is your newsletter.

' text_content: type: string example: 'Hello @{{first_name}}, here is your newsletter.' mjml_content: type: string example: null variables: type: array example: - key: first_name label: 'First Name' default: there required: true items: type: object properties: key: type: string example: first_name label: type: string example: 'First Name' default: type: string example: there required: type: boolean example: true message: type: string example: 'Template created.' 422: description: '' content: application/json: schema: oneOf: - description: 'Slug Already Exists' type: object example: success: false error: code: SLUG_EXISTS message: 'A template with this slug already exists' properties: success: type: boolean example: false error: type: object properties: code: type: string example: SLUG_EXISTS message: type: string example: 'A template with this slug already exists' - description: 'Validation Error' type: object example: success: false error: code: VALIDATION_ERROR message: 'The given data was invalid' details: name: - 'The name field is required.' category: - 'The selected category is invalid.' properties: success: type: boolean example: false error: type: object properties: code: type: string example: VALIDATION_ERROR message: type: string example: 'The given data was invalid' details: type: object properties: name: type: array example: - 'The name field is required.' items: type: string category: type: array example: - 'The selected category is invalid.' items: type: string tags: - 'Email Templates' requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Template display name.' example: 'Monthly Newsletter' slug: type: string description: 'Unique URL-safe identifier for the template.' example: monthly-newsletter category: type: string description: 'Template category. Options: transactional, marketing, notification, onboarding, custom.' example: marketing subject_template: type: string description: 'Subject line with optional merge tags.' example: 'Your @{{month}} Newsletter' html_content: type: string description: 'HTML body of the template with optional merge tags.' example: '

Hello @{{first_name}}

Here is your newsletter.

' text_content: type: string description: 'optional Plain-text version of the template. Auto-generated from HTML if omitted.' example: 'Hello @{{first_name}}, here is your newsletter.' nullable: true mjml_content: type: string description: 'optional MJML source (for GrapesJS editor).' example: 'Hello' nullable: true description: type: string description: "optional Internal description of the template's purpose." example: 'Sent monthly with latest updates' nullable: true variables: type: array description: 'optional Array of variable definitions for the template.' example: - architecto items: type: string nullable: true required: - name - slug - category - subject_template - html_content '/api/v1/email-templates/{id}': get: summary: 'Get Template Details' operationId: getTemplateDetails description: "Retrieve full details of a specific email template, including HTML content,\ntext content, MJML source, and variable definitions." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'Welcome Email' slug: welcome-email category: onboarding subject_template: 'Welcome, @{{first_name}}!' is_system: false is_active: true version: 1 times_used: 150 created_at: '2026-01-10T10:00:00+00:00' updated_at: '2026-01-12T14:30:00+00:00' description: 'Sent to new users after registration' html_content: '

Welcome, @{{first_name}}!

Thanks for joining.

' text_content: 'Welcome, @{{first_name}}! Thanks for joining.' mjml_content: null variables: - key: first_name label: 'First Name' default: there required: true properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'Welcome Email' slug: type: string example: welcome-email category: type: string example: onboarding subject_template: type: string example: 'Welcome, @{{first_name}}!' is_system: type: boolean example: false is_active: type: boolean example: true version: type: integer example: 1 times_used: type: integer example: 150 created_at: type: string example: '2026-01-10T10:00:00+00:00' updated_at: type: string example: '2026-01-12T14:30:00+00:00' description: type: string example: 'Sent to new users after registration' html_content: type: string example: '

Welcome, @{{first_name}}!

Thanks for joining.

' text_content: type: string example: 'Welcome, @{{first_name}}! Thanks for joining.' mjml_content: type: string example: null variables: type: array example: - key: first_name label: 'First Name' default: there required: true items: type: object properties: key: type: string example: first_name label: type: string example: 'First Name' default: type: string example: there required: type: boolean example: true 404: description: 'Template Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Template not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Template not found' tags: - 'Email Templates' put: summary: 'Update Template' operationId: updateTemplate description: "Update an existing email template. Only organization-owned templates can be edited;\nsystem templates require super admin privileges. All fields are optional; only\nprovided fields will be updated." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'Monthly Newsletter v2' slug: monthly-newsletter category: marketing subject_template: 'Your @{{month}} Newsletter - Updated' is_system: false is_active: true version: 1 times_used: 150 created_at: '2026-01-10T10:00:00+00:00' updated_at: '2026-01-15T14:30:00+00:00' description: 'Updated monthly newsletter' html_content: '

Hello @{{first_name}}

New content.

' text_content: 'Hello @{{first_name}}, new content.' mjml_content: null variables: [] message: 'Template updated.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'Monthly Newsletter v2' slug: type: string example: monthly-newsletter category: type: string example: marketing subject_template: type: string example: 'Your @{{month}} Newsletter - Updated' is_system: type: boolean example: false is_active: type: boolean example: true version: type: integer example: 1 times_used: type: integer example: 150 created_at: type: string example: '2026-01-10T10:00:00+00:00' updated_at: type: string example: '2026-01-15T14:30:00+00:00' description: type: string example: 'Updated monthly newsletter' html_content: type: string example: '

Hello @{{first_name}}

New content.

' text_content: type: string example: 'Hello @{{first_name}}, new content.' mjml_content: type: string example: null variables: type: array example: [] message: type: string example: 'Template updated.' 403: description: 'System Template' content: application/json: schema: type: object example: success: false error: code: FORBIDDEN message: 'System templates cannot be edited' properties: success: type: boolean example: false error: type: object properties: code: type: string example: FORBIDDEN message: type: string example: 'System templates cannot be edited' 404: description: 'Template Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Template not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Template not found' tags: - 'Email Templates' requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: 'optional Updated template name.' example: 'Monthly Newsletter v2' category: type: string description: 'optional Updated category. Options: transactional, marketing, notification, onboarding, custom.' example: marketing subject_template: type: string description: 'optional Updated subject line.' example: 'Your @{{month}} Newsletter - Updated' html_content: type: string description: 'optional Updated HTML body.' example: '

Hello @{{first_name}}

New content.

' text_content: type: string description: 'optional Updated plain-text body.' example: 'Hello @{{first_name}}, new content.' nullable: true description: type: string description: 'optional Updated description.' example: 'Updated monthly newsletter' nullable: true variables: type: array description: 'optional Updated variable definitions.' example: - architecto items: type: string nullable: true is_active: type: boolean description: 'optional Whether the template is active.' example: true delete: summary: 'Delete Template' operationId: deleteTemplate description: "Soft-delete an email template. System templates require super admin privileges\nto delete. Deleted templates are no longer available for sending but their\ndata is preserved for historical reference." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: null message: 'Template deleted.' properties: success: type: boolean example: true data: type: string example: null message: type: string example: 'Template deleted.' 403: description: 'System Template' content: application/json: schema: type: object example: success: false error: code: FORBIDDEN message: 'System templates cannot be deleted' properties: success: type: boolean example: false error: type: object properties: code: type: string example: FORBIDDEN message: type: string example: 'System templates cannot be deleted' 404: description: 'Template Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Template not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Template not found' tags: - 'Email Templates' parameters: - in: path name: id description: 'The UUID of the template.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/email-templates/{id}/preview': post: summary: 'Preview Template' operationId: previewTemplate description: "Render a template with sample data and return the resulting subject, HTML, and\nplain-text content. Useful for previewing how merge tags will be replaced before\nsending. Pass variable values in the `data` object." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: subject: 'Welcome, John!' html_content: '

Welcome, John!

Thanks for joining.

' text_content: 'Welcome, John! Thanks for joining.' properties: success: type: boolean example: true data: type: object properties: subject: type: string example: 'Welcome, John!' html_content: type: string example: '

Welcome, John!

Thanks for joining.

' text_content: type: string example: 'Welcome, John! Thanks for joining.' 404: description: 'Template Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Template not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Template not found' tags: - 'Email Templates' requestBody: required: false content: application/json: schema: type: object properties: data: type: object description: 'optional Key-value pairs of template variables to substitute.' example: first_name: John month: January properties: { } parameters: - in: path name: id description: 'The UUID of the template.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/email-templates/{id}/duplicate': post: summary: 'Duplicate Template' operationId: duplicateTemplate description: "Create a copy of an existing template in your organization. The copy will have\n\" (Copy)\" appended to its name and a unique slug with a timestamp suffix.\nUseful for customizing system templates or creating variations of existing ones." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true data: id: 9b8f3e6d-2345-6789-01bc-def123456789 name: 'Welcome Email (Copy)' slug: welcome-email-copy-1705312200 category: onboarding subject_template: 'Welcome, @{{first_name}}!' is_system: false is_active: true version: 1 times_used: 0 created_at: '2026-01-15T10:30:00+00:00' updated_at: '2026-01-15T10:30:00+00:00' description: 'Sent to new users after registration' html_content: '

Welcome, @{{first_name}}!

Thanks for joining.

' text_content: 'Welcome, @{{first_name}}! Thanks for joining.' mjml_content: null variables: - key: first_name label: 'First Name' default: there required: true message: 'Template duplicated.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9b8f3e6d-2345-6789-01bc-def123456789 name: type: string example: 'Welcome Email (Copy)' slug: type: string example: welcome-email-copy-1705312200 category: type: string example: onboarding subject_template: type: string example: 'Welcome, @{{first_name}}!' is_system: type: boolean example: false is_active: type: boolean example: true version: type: integer example: 1 times_used: type: integer example: 0 created_at: type: string example: '2026-01-15T10:30:00+00:00' updated_at: type: string example: '2026-01-15T10:30:00+00:00' description: type: string example: 'Sent to new users after registration' html_content: type: string example: '

Welcome, @{{first_name}}!

Thanks for joining.

' text_content: type: string example: 'Welcome, @{{first_name}}! Thanks for joining.' mjml_content: type: string example: null variables: type: array example: - key: first_name label: 'First Name' default: there required: true items: type: object properties: key: type: string example: first_name label: type: string example: 'First Name' default: type: string example: there required: type: boolean example: true message: type: string example: 'Template duplicated.' 404: description: 'Template Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Template not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Template not found' tags: - 'Email Templates' parameters: - in: path name: id description: 'The UUID of the template to duplicate.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string /api/v1/campaigns: get: summary: 'List Campaigns' operationId: listCampaigns description: "Retrieve a paginated list of all email campaigns for your organization, sorted by newest first.\nOptionally filter by campaign status." parameters: - in: query name: status description: 'Filter by campaign status. Options: draft, scheduled, sending, paused, completed, cancelled, failed.' example: draft required: false schema: type: string description: 'Filter by campaign status. Options: draft, scheduled, sending, paused, completed, cancelled, failed.' example: draft - in: query name: per_page description: 'Number of results per page (default 25).' example: 10 required: false schema: type: integer description: 'Number of results per page (default 25).' example: 10 responses: 200: description: '' content: application/json: schema: type: object example: success: true data: data: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'January Newsletter' status: completed subject: 'Your January Update' from_email: hello@exosend.com total_recipients: 5000 total_sent: 4980 total_opened: 2100 total_clicked: 450 total_bounced: 20 created_at: '2026-01-10T10:00:00+00:00' pagination: total: 15 per_page: 25 current_page: 1 last_page: 1 properties: success: type: boolean example: true data: type: object properties: data: type: array example: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'January Newsletter' status: completed subject: 'Your January Update' from_email: hello@exosend.com total_recipients: 5000 total_sent: 4980 total_opened: 2100 total_clicked: 450 total_bounced: 20 created_at: '2026-01-10T10:00:00+00:00' items: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'January Newsletter' status: type: string example: completed subject: type: string example: 'Your January Update' from_email: type: string example: hello@exosend.com total_recipients: type: integer example: 5000 total_sent: type: integer example: 4980 total_opened: type: integer example: 2100 total_clicked: type: integer example: 450 total_bounced: type: integer example: 20 created_at: type: string example: '2026-01-10T10:00:00+00:00' pagination: type: object properties: total: type: integer example: 15 per_page: type: integer example: 25 current_page: type: integer example: 1 last_page: type: integer example: 1 tags: - 'Email Campaigns' post: summary: 'Create Campaign' operationId: createCampaign description: "Create a new email campaign in draft status. The campaign can then be sent immediately\nor scheduled for later delivery. You can specify target contact lists and optionally\nuse a pre-built template." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'January Newsletter' status: draft subject: 'Your January Update' from_email: hello@exosend.com from_name: 'ExoSend Team' total_recipients: 0 total_sent: 0 created_at: '2026-01-15T10:00:00+00:00' message: 'Campaign created.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'January Newsletter' status: type: string example: draft subject: type: string example: 'Your January Update' from_email: type: string example: hello@exosend.com from_name: type: string example: 'ExoSend Team' total_recipients: type: integer example: 0 total_sent: type: integer example: 0 created_at: type: string example: '2026-01-15T10:00:00+00:00' message: type: string example: 'Campaign created.' 422: description: '' content: application/json: schema: oneOf: - description: 'Invalid Target List' type: object example: success: false error: code: INVALID_LIST message: 'One or more target lists do not exist or do not belong to your organization' properties: success: type: boolean example: false error: type: object properties: code: type: string example: INVALID_LIST message: type: string example: 'One or more target lists do not exist or do not belong to your organization' - description: 'Validation Error' type: object example: success: false error: code: VALIDATION_ERROR message: 'The given data was invalid' details: name: - 'The name field is required.' subject: - 'The subject field is required.' properties: success: type: boolean example: false error: type: object properties: code: type: string example: VALIDATION_ERROR message: type: string example: 'The given data was invalid' details: type: object properties: name: type: array example: - 'The name field is required.' items: type: string subject: type: array example: - 'The subject field is required.' items: type: string tags: - 'Email Campaigns' requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Campaign name for internal reference.' example: 'January Newsletter' description: type: string description: 'optional Internal description of the campaign.' example: 'Monthly newsletter for January 2026' nullable: true email_template_id: type: string description: 'optional UUID of an email template to use.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 nullable: true subject: type: string description: 'Email subject line for the campaign.' example: 'Your January Update' html_content: type: string description: 'optional HTML body of the email (if not using a template).' example: '

January Update

Here are our latest updates.

' nullable: true text_content: type: string description: 'optional Plain-text body of the email.' example: 'Here are our latest updates.' nullable: true template_variables: type: object description: 'optional Variables to merge into the template.' example: month: January year: '2026' properties: { } nullable: true email_domain_id: type: string description: 'optional UUID of the email domain to send from.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 nullable: true from_email: type: string description: 'Sender email address.' example: hello@exosend.com from_name: type: string description: 'optional Sender display name.' example: 'ExoSend Team' nullable: true reply_to: type: string description: 'optional Reply-to email address.' example: support@exosend.com nullable: true target_list_ids: type: array description: 'optional Array of contact list UUIDs to send to.' example: - 9a7f2e5c-1234-5678-90ab-cdef12345678 items: type: string segment_filters: type: object description: 'optional Segment filter criteria for targeting contacts.' example: status: active properties: { } nullable: true send_rate_per_minute: type: integer description: 'optional Throttle: maximum emails per minute.' example: 500 nullable: true batch_size: type: integer description: 'optional Number of emails per batch (1-1000).' example: 100 nullable: true timezone: type: string description: '' example: Asia/Yekaterinburg nullable: true scheduled_at: type: string description: 'Must be a valid date.' example: '2026-03-04T23:25:31' nullable: true required: - name - subject - from_email '/api/v1/campaigns/{id}': get: summary: 'Get Campaign Details' operationId: getCampaignDetails description: "Retrieve full details of a specific campaign, including computed engagement rates\n(open rate, click rate, bounce rate) if the campaign has been sent." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'January Newsletter' status: completed subject: 'Your January Update' from_email: hello@exosend.com from_name: 'ExoSend Team' total_recipients: 5000 total_sent: 4980 total_opened: 2100 total_clicked: 450 total_bounced: 20 open_rate: 42.2% click_rate: 9.0% bounce_rate: 0.4% created_at: '2026-01-10T10:00:00+00:00' sent_at: '2026-01-10T12:00:00+00:00' completed_at: '2026-01-10T13:30:00+00:00' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'January Newsletter' status: type: string example: completed subject: type: string example: 'Your January Update' from_email: type: string example: hello@exosend.com from_name: type: string example: 'ExoSend Team' total_recipients: type: integer example: 5000 total_sent: type: integer example: 4980 total_opened: type: integer example: 2100 total_clicked: type: integer example: 450 total_bounced: type: integer example: 20 open_rate: type: string example: 42.2% click_rate: type: string example: 9.0% bounce_rate: type: string example: 0.4% created_at: type: string example: '2026-01-10T10:00:00+00:00' sent_at: type: string example: '2026-01-10T12:00:00+00:00' completed_at: type: string example: '2026-01-10T13:30:00+00:00' 404: description: 'Campaign Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Campaign not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Campaign not found' tags: - 'Email Campaigns' put: summary: 'Update Campaign' operationId: updateCampaign description: "Update an existing campaign. Only campaigns in draft status can be updated.\nAll fields are optional; only provided fields will be modified." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'January Newsletter v2' status: draft subject: 'Your Updated January Newsletter' from_email: newsletter@exosend.com created_at: '2026-01-10T10:00:00+00:00' updated_at: '2026-01-15T14:30:00+00:00' message: 'Campaign updated.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'January Newsletter v2' status: type: string example: draft subject: type: string example: 'Your Updated January Newsletter' from_email: type: string example: newsletter@exosend.com created_at: type: string example: '2026-01-10T10:00:00+00:00' updated_at: type: string example: '2026-01-15T14:30:00+00:00' message: type: string example: 'Campaign updated.' 404: description: 'Campaign Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Campaign not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Campaign not found' 422: description: '' content: application/json: schema: oneOf: - description: 'Not Draft Status' type: object example: success: false error: code: INVALID_STATUS message: 'Only draft campaigns can be updated' properties: success: type: boolean example: false error: type: object properties: code: type: string example: INVALID_STATUS message: type: string example: 'Only draft campaigns can be updated' - description: 'Invalid Target List' type: object example: success: false error: code: INVALID_LIST message: 'One or more target lists do not exist or do not belong to your organization' properties: success: type: boolean example: false error: type: object properties: code: type: string example: INVALID_LIST message: type: string example: 'One or more target lists do not exist or do not belong to your organization' tags: - 'Email Campaigns' requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: 'optional Updated campaign name.' example: 'January Newsletter v2' description: type: string description: 'optional Updated description.' example: 'Revised newsletter content' nullable: true email_template_id: type: string description: 'optional UUID of template to use.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 nullable: true subject: type: string description: 'optional Updated subject line.' example: 'Your Updated January Newsletter' html_content: type: string description: 'optional Updated HTML body.' example: '

Updated Content

' nullable: true text_content: type: string description: 'optional Updated plain-text body.' example: 'Updated content.' nullable: true template_variables: type: object description: 'optional Updated template variables.' example: month: February properties: { } nullable: true from_email: type: string description: 'optional Updated sender email.' example: newsletter@exosend.com from_name: type: string description: 'optional Updated sender name.' example: 'ExoSend Newsletter' nullable: true reply_to: type: string description: 'optional Updated reply-to address.' example: support@exosend.com nullable: true target_list_ids: type: array description: 'optional Updated target contact list UUIDs.' example: - 9a7f2e5c-1234-5678-90ab-cdef12345678 items: type: string send_rate_per_minute: type: integer description: 'optional Updated send rate throttle.' example: 300 nullable: true timezone: type: string description: '' example: Asia/Yekaterinburg nullable: true scheduled_at: type: string description: 'Must be a valid date.' example: '2026-03-04T23:25:31' nullable: true delete: summary: 'Delete Campaign' operationId: deleteCampaign description: "Permanently delete a draft campaign. Only campaigns in draft status can be deleted.\nCampaigns that have been sent, scheduled, or completed cannot be deleted." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: null message: 'Campaign deleted.' properties: success: type: boolean example: true data: type: string example: null message: type: string example: 'Campaign deleted.' 404: description: 'Campaign Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Campaign not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Campaign not found' 422: description: 'Not Draft Status' content: application/json: schema: type: object example: success: false error: code: INVALID_STATUS message: 'Only draft campaigns can be deleted' properties: success: type: boolean example: false error: type: object properties: code: type: string example: INVALID_STATUS message: type: string example: 'Only draft campaigns can be deleted' tags: - 'Email Campaigns' parameters: - in: path name: id description: 'The UUID of the campaign.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/campaigns/{id}/send': post: summary: 'Send Campaign' operationId: sendCampaign description: "Start sending a draft campaign immediately. The campaign status will change to \"sending\"\nand emails will be dispatched to all target recipients asynchronously." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'January Newsletter' status: sending total_recipients: 5000 sent_at: '2026-01-15T12:00:00+00:00' message: 'Campaign sending started.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'January Newsletter' status: type: string example: sending total_recipients: type: integer example: 5000 sent_at: type: string example: '2026-01-15T12:00:00+00:00' message: type: string example: 'Campaign sending started.' 404: description: 'Campaign Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Campaign not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Campaign not found' 422: description: 'Not Draft Status' content: application/json: schema: type: object example: success: false error: code: INVALID_STATUS message: 'Only draft campaigns can be sent' properties: success: type: boolean example: false error: type: object properties: code: type: string example: INVALID_STATUS message: type: string example: 'Only draft campaigns can be sent' tags: - 'Email Campaigns' parameters: - in: path name: id description: 'The UUID of the campaign.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/campaigns/{id}/schedule': post: summary: 'Schedule Campaign' operationId: scheduleCampaign description: "Schedule a draft campaign for future delivery at the specified date and time.\nThe campaign status will change to \"scheduled\" and sending will begin automatically\nat the scheduled time." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'January Newsletter' status: scheduled scheduled_at: '2026-02-01T09:00:00+00:00' message: 'Campaign scheduled.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'January Newsletter' status: type: string example: scheduled scheduled_at: type: string example: '2026-02-01T09:00:00+00:00' message: type: string example: 'Campaign scheduled.' 404: description: 'Campaign Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Campaign not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Campaign not found' 422: description: '' content: application/json: schema: oneOf: - description: 'Not Draft Status' type: object example: success: false error: code: INVALID_STATUS message: 'Only draft campaigns can be scheduled' properties: success: type: boolean example: false error: type: object properties: code: type: string example: INVALID_STATUS message: type: string example: 'Only draft campaigns can be scheduled' - description: 'Past Date' type: object example: success: false error: code: VALIDATION_ERROR message: 'The given data was invalid' details: scheduled_at: - 'The scheduled at must be a date after now.' properties: success: type: boolean example: false error: type: object properties: code: type: string example: VALIDATION_ERROR message: type: string example: 'The given data was invalid' details: type: object properties: scheduled_at: type: array example: - 'The scheduled at must be a date after now.' items: type: string tags: - 'Email Campaigns' requestBody: required: true content: application/json: schema: type: object properties: scheduled_at: type: string description: 'Future date and time for sending (ISO 8601 format). Must be after the current time.' example: '2026-02-01T09:00:00Z' timezone: type: string description: '' example: Asia/Yekaterinburg nullable: true required: - scheduled_at parameters: - in: path name: id description: 'The UUID of the campaign.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/campaigns/{id}/pause': post: summary: 'Pause Campaign' operationId: pauseCampaign description: "Pause a campaign that is currently sending. Emails already queued will still be\ndelivered, but no new emails will be dispatched until the campaign is resumed." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'January Newsletter' status: paused total_sent: 2500 total_recipients: 5000 message: 'Campaign paused.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'January Newsletter' status: type: string example: paused total_sent: type: integer example: 2500 total_recipients: type: integer example: 5000 message: type: string example: 'Campaign paused.' 404: description: 'Campaign Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Campaign not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Campaign not found' 422: description: 'Not Sending Status' content: application/json: schema: type: object example: success: false error: code: INVALID_STATUS message: 'Only sending campaigns can be paused' properties: success: type: boolean example: false error: type: object properties: code: type: string example: INVALID_STATUS message: type: string example: 'Only sending campaigns can be paused' tags: - 'Email Campaigns' parameters: - in: path name: id description: 'The UUID of the campaign.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/campaigns/{id}/resume': post: summary: 'Resume Campaign' operationId: resumeCampaign description: "Resume a previously paused campaign. Email dispatch will continue from where\nit left off, sending to remaining recipients." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'January Newsletter' status: sending total_sent: 2500 total_recipients: 5000 message: 'Campaign resumed.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'January Newsletter' status: type: string example: sending total_sent: type: integer example: 2500 total_recipients: type: integer example: 5000 message: type: string example: 'Campaign resumed.' 404: description: 'Campaign Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Campaign not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Campaign not found' 422: description: 'Not Paused Status' content: application/json: schema: type: object example: success: false error: code: INVALID_STATUS message: 'Only paused campaigns can be resumed' properties: success: type: boolean example: false error: type: object properties: code: type: string example: INVALID_STATUS message: type: string example: 'Only paused campaigns can be resumed' tags: - 'Email Campaigns' parameters: - in: path name: id description: 'The UUID of the campaign.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/campaigns/{id}/cancel': post: summary: 'Cancel Campaign' operationId: cancelCampaign description: "Cancel a campaign that is in draft, scheduled, sending, or paused status.\nCompleted or already-cancelled campaigns cannot be cancelled. Emails already\nsent will not be affected." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'January Newsletter' status: cancelled total_sent: 2500 total_recipients: 5000 message: 'Campaign cancelled.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'January Newsletter' status: type: string example: cancelled total_sent: type: integer example: 2500 total_recipients: type: integer example: 5000 message: type: string example: 'Campaign cancelled.' 404: description: 'Campaign Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Campaign not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Campaign not found' 422: description: 'Invalid Status' content: application/json: schema: type: object example: success: false error: code: INVALID_STATUS message: 'This campaign cannot be cancelled' properties: success: type: boolean example: false error: type: object properties: code: type: string example: INVALID_STATUS message: type: string example: 'This campaign cannot be cancelled' tags: - 'Email Campaigns' parameters: - in: path name: id description: 'The UUID of the campaign.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string /api/v1/contacts: get: summary: 'List Contacts' operationId: listContacts description: "Retrieve a paginated list of all contacts for your organization. Supports filtering\nby status and full-text search across email, first name, and last name." parameters: - in: query name: status description: 'Filter by contact status. Options: active, unsubscribed, bounced, complained.' example: active required: false schema: type: string description: 'Filter by contact status. Options: active, unsubscribed, bounced, complained.' example: active - in: query name: search description: 'Search contacts by email, first name, or last name.' example: john required: false schema: type: string description: 'Search contacts by email, first name, or last name.' example: john - in: query name: per_page description: 'Number of results per page (default 25).' example: 50 required: false schema: type: integer description: 'Number of results per page (default 25).' example: 50 responses: 200: description: '' content: application/json: schema: type: object example: success: true data: data: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 email: john@example.com first_name: John last_name: Doe status: active custom_fields: company: 'Acme Inc' subscribed_at: '2026-01-10T10:00:00+00:00' created_at: '2026-01-10T10:00:00+00:00' pagination: total: 1250 per_page: 25 current_page: 1 last_page: 50 properties: success: type: boolean example: true data: type: object properties: data: type: array example: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 email: john@example.com first_name: John last_name: Doe status: active custom_fields: company: 'Acme Inc' subscribed_at: '2026-01-10T10:00:00+00:00' created_at: '2026-01-10T10:00:00+00:00' items: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 email: type: string example: john@example.com first_name: type: string example: John last_name: type: string example: Doe status: type: string example: active custom_fields: type: object properties: company: type: string example: 'Acme Inc' subscribed_at: type: string example: '2026-01-10T10:00:00+00:00' created_at: type: string example: '2026-01-10T10:00:00+00:00' pagination: type: object properties: total: type: integer example: 1250 per_page: type: integer example: 25 current_page: type: integer example: 1 last_page: type: integer example: 50 tags: - Contacts post: summary: 'Create Contact' operationId: createContact description: "Add a new contact to your organization. The contact will be automatically subscribed\nupon creation. Each email address must be unique within the organization." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 email: john@example.com first_name: John last_name: Doe status: active custom_fields: company: 'Acme Inc' role: Developer subscribed_at: '2026-01-15T10:00:00+00:00' created_at: '2026-01-15T10:00:00+00:00' message: 'Contact created.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 email: type: string example: john@example.com first_name: type: string example: John last_name: type: string example: Doe status: type: string example: active custom_fields: type: object properties: company: type: string example: 'Acme Inc' role: type: string example: Developer subscribed_at: type: string example: '2026-01-15T10:00:00+00:00' created_at: type: string example: '2026-01-15T10:00:00+00:00' message: type: string example: 'Contact created.' 422: description: '' content: application/json: schema: oneOf: - description: 'Contact Already Exists' type: object example: success: false error: code: CONTACT_EXISTS message: 'A contact with this email already exists' properties: success: type: boolean example: false error: type: object properties: code: type: string example: CONTACT_EXISTS message: type: string example: 'A contact with this email already exists' - description: 'Validation Error' type: object example: success: false error: code: VALIDATION_ERROR message: 'The given data was invalid' details: email: - 'The email field is required.' properties: success: type: boolean example: false error: type: object properties: code: type: string example: VALIDATION_ERROR message: type: string example: 'The given data was invalid' details: type: object properties: email: type: array example: - 'The email field is required.' items: type: string tags: - Contacts requestBody: required: true content: application/json: schema: type: object properties: email: type: string description: "Contact's email address. Must be unique within your organization." example: john@example.com first_name: type: string description: "optional Contact's first name." example: John nullable: true last_name: type: string description: "optional Contact's last name." example: Doe nullable: true custom_fields: type: object description: 'optional Custom key-value fields for the contact.' example: company: 'Acme Inc' role: Developer properties: { } nullable: true required: - email '/api/v1/contacts/{id}': put: summary: 'Update Contact' operationId: updateContact description: "Update an existing contact's information. All fields are optional; only provided\nfields will be modified." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 email: newemail@example.com first_name: Jane last_name: Smith status: active custom_fields: company: 'New Corp' subscribed_at: '2026-01-10T10:00:00+00:00' created_at: '2026-01-10T10:00:00+00:00' updated_at: '2026-01-15T14:30:00+00:00' message: 'Contact updated.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 email: type: string example: newemail@example.com first_name: type: string example: Jane last_name: type: string example: Smith status: type: string example: active custom_fields: type: object properties: company: type: string example: 'New Corp' subscribed_at: type: string example: '2026-01-10T10:00:00+00:00' created_at: type: string example: '2026-01-10T10:00:00+00:00' updated_at: type: string example: '2026-01-15T14:30:00+00:00' message: type: string example: 'Contact updated.' 404: description: 'Contact Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Contact not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Contact not found' tags: - Contacts requestBody: required: false content: application/json: schema: type: object properties: email: type: string description: 'optional Updated email address.' example: newemail@example.com first_name: type: string description: 'optional Updated first name.' example: Jane nullable: true last_name: type: string description: 'optional Updated last name.' example: Smith nullable: true custom_fields: type: object description: 'optional Updated custom fields.' example: company: 'New Corp' properties: { } nullable: true status: type: string description: 'optional Updated status. Options: active, unsubscribed, bounced, complained.' example: unsubscribed delete: summary: 'Delete Contact' operationId: deleteContact description: "Permanently delete a contact from your organization. This also removes the contact\nfrom all associated contact lists." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: null message: 'Contact deleted.' properties: success: type: boolean example: true data: type: string example: null message: type: string example: 'Contact deleted.' 404: description: 'Contact Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Contact not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Contact not found' tags: - Contacts parameters: - in: path name: id description: 'The UUID of the contact.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string /api/v1/contacts/import: post: summary: 'Import Contacts from CSV' operationId: importContactsFromCSV description: "Import contacts from a CSV file. The CSV should have columns for email, first_name,\nand last_name (at minimum). Duplicate emails are automatically skipped. Optionally\nassign all imported contacts to a specific contact list. Maximum file size: 10MB." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: imported: 450 skipped: 12 errors: - row: 5 error: 'Invalid email format' - row: 23 error: 'Missing required email field' message: 'Import completed.' properties: success: type: boolean example: true data: type: object properties: imported: type: integer example: 450 skipped: type: integer example: 12 errors: type: array example: - row: 5 error: 'Invalid email format' - row: 23 error: 'Missing required email field' items: type: object properties: row: type: integer example: 5 error: type: string example: 'Invalid email format' message: type: string example: 'Import completed.' 422: description: 'Validation Error' content: application/json: schema: type: object example: success: false error: code: VALIDATION_ERROR message: 'The given data was invalid' details: file: - 'The file field is required.' list_id: - 'The list id must be a valid UUID.' properties: success: type: boolean example: false error: type: object properties: code: type: string example: VALIDATION_ERROR message: type: string example: 'The given data was invalid' details: type: object properties: file: type: array example: - 'The file field is required.' items: type: string list_id: type: array example: - 'The list id must be a valid UUID.' items: type: string tags: - Contacts requestBody: required: true content: multipart/form-data: schema: type: object properties: file: type: string format: binary description: 'CSV file containing contacts. Accepted formats: csv, txt. Maximum 10MB.' list_id: type: string description: 'optional UUID of a contact list to add imported contacts to.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 nullable: true required: - file /api/v1/contact-lists: get: summary: 'List Contact Lists' operationId: listContactLists description: "Retrieve a paginated list of all contact lists for your organization, including\nthe number of contacts in each list." parameters: - in: query name: per_page description: 'Number of results per page (default 25).' example: 10 required: false schema: type: integer description: 'Number of results per page (default 25).' example: 10 responses: 200: description: '' content: application/json: schema: type: object example: success: true data: data: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'Newsletter Subscribers' description: 'Users who opted in to the monthly newsletter' is_default: true contacts_count: 3500 created_at: '2026-01-10T10:00:00+00:00' updated_at: '2026-01-15T14:30:00+00:00' pagination: total: 8 per_page: 25 current_page: 1 last_page: 1 properties: success: type: boolean example: true data: type: object properties: data: type: array example: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'Newsletter Subscribers' description: 'Users who opted in to the monthly newsletter' is_default: true contacts_count: 3500 created_at: '2026-01-10T10:00:00+00:00' updated_at: '2026-01-15T14:30:00+00:00' items: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'Newsletter Subscribers' description: type: string example: 'Users who opted in to the monthly newsletter' is_default: type: boolean example: true contacts_count: type: integer example: 3500 created_at: type: string example: '2026-01-10T10:00:00+00:00' updated_at: type: string example: '2026-01-15T14:30:00+00:00' pagination: type: object properties: total: type: integer example: 8 per_page: type: integer example: 25 current_page: type: integer example: 1 last_page: type: integer example: 1 tags: - 'Contact Lists' post: summary: 'Create Contact List' operationId: createContactList description: "Create a new contact list for your organization. List names must be unique\nwithin the organization." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'Newsletter Subscribers' description: 'Users who opted in to the monthly newsletter' is_default: false created_at: '2026-01-15T10:00:00+00:00' updated_at: '2026-01-15T10:00:00+00:00' message: 'Contact list created.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'Newsletter Subscribers' description: type: string example: 'Users who opted in to the monthly newsletter' is_default: type: boolean example: false created_at: type: string example: '2026-01-15T10:00:00+00:00' updated_at: type: string example: '2026-01-15T10:00:00+00:00' message: type: string example: 'Contact list created.' 422: description: '' content: application/json: schema: oneOf: - description: 'List Already Exists' type: object example: success: false error: code: LIST_EXISTS message: 'A list with this name already exists' properties: success: type: boolean example: false error: type: object properties: code: type: string example: LIST_EXISTS message: type: string example: 'A list with this name already exists' - description: 'Validation Error' type: object example: success: false error: code: VALIDATION_ERROR message: 'The given data was invalid' details: name: - 'The name field is required.' properties: success: type: boolean example: false error: type: object properties: code: type: string example: VALIDATION_ERROR message: type: string example: 'The given data was invalid' details: type: object properties: name: type: array example: - 'The name field is required.' items: type: string tags: - 'Contact Lists' requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'List name. Must be unique within your organization.' example: 'Newsletter Subscribers' description: type: string description: "optional Description of the list's purpose." example: 'Users who opted in to the monthly newsletter' nullable: true is_default: type: boolean description: 'optional Whether this should be the default list for new contacts. Default: false.' example: false required: - name '/api/v1/contact-lists/{id}': put: summary: 'Update Contact List' operationId: updateContactList description: "Update an existing contact list's name, description, or default status.\nAll fields are optional; only provided fields will be modified." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: 'Premium Subscribers' description: 'Premium tier newsletter subscribers' is_default: true created_at: '2026-01-10T10:00:00+00:00' updated_at: '2026-01-15T14:30:00+00:00' message: 'Contact list updated.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 name: type: string example: 'Premium Subscribers' description: type: string example: 'Premium tier newsletter subscribers' is_default: type: boolean example: true created_at: type: string example: '2026-01-10T10:00:00+00:00' updated_at: type: string example: '2026-01-15T14:30:00+00:00' message: type: string example: 'Contact list updated.' 404: description: 'List Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Contact list not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Contact list not found' tags: - 'Contact Lists' requestBody: required: false content: application/json: schema: type: object properties: name: type: string description: 'optional Updated list name.' example: 'Premium Subscribers' description: type: string description: 'optional Updated description.' example: 'Premium tier newsletter subscribers' nullable: true is_default: type: boolean description: 'optional Whether this is the default list.' example: true delete: summary: 'Delete Contact List' operationId: deleteContactList description: "Delete a contact list. This removes the list itself but does not delete\nthe contacts that were in the list. Contacts will remain in the organization." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: null message: 'Contact list deleted.' properties: success: type: boolean example: true data: type: string example: null message: type: string example: 'Contact list deleted.' 404: description: 'List Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Contact list not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Contact list not found' tags: - 'Contact Lists' parameters: - in: path name: id description: 'The UUID of the contact list.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/contact-lists/{id}/contacts': post: summary: 'Add Contacts to List' operationId: addContactsToList description: "Add one or more existing contacts to a contact list. Contacts that are already\nin the list will be silently skipped (no duplicates). All contact IDs must\nbelong to your organization." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: null message: 'Contacts added to list.' properties: success: type: boolean example: true data: type: string example: null message: type: string example: 'Contacts added to list.' 404: description: 'List Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Contact list not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Contact list not found' 422: description: 'Validation Error' content: application/json: schema: type: object example: success: false error: code: VALIDATION_ERROR message: 'The given data was invalid' details: contact_ids: - 'The contact ids field is required.' properties: success: type: boolean example: false error: type: object properties: code: type: string example: VALIDATION_ERROR message: type: string example: 'The given data was invalid' details: type: object properties: contact_ids: type: array example: - 'The contact ids field is required.' items: type: string tags: - 'Contact Lists' requestBody: required: true content: application/json: schema: type: object properties: contact_ids: type: array description: 'Array of contact UUIDs to add to the list (minimum 1).' example: - 9a7f2e5c-1234-5678-90ab-cdef12345678 - 9b8f3e6d-2345-6789-01bc-def123456789 items: type: string required: - contact_ids parameters: - in: path name: id description: 'The UUID of the contact list.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/contact-lists/{id}/contacts/{contactId}': delete: summary: 'Remove Contact from List' operationId: removeContactFromList description: "Remove a single contact from a contact list. The contact itself is not deleted\nand remains in the organization. If the contact is not in the list, the\noperation completes silently." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: null message: 'Contact removed from list.' properties: success: type: boolean example: true data: type: string example: null message: type: string example: 'Contact removed from list.' 404: description: 'List Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Contact list not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Contact list not found' tags: - 'Contact Lists' parameters: - in: path name: id description: 'The UUID of the contact list.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string - in: path name: contactId description: 'The UUID of the contact to remove.' example: 9b8f3e6d-2345-6789-01bc-def123456789 required: true schema: type: string /api/v1/analytics/overview: get: summary: 'Organization Overview' operationId: organizationOverview description: "Retrieve aggregate email analytics for your organization over a specified date range.\nIncludes totals for sent, delivered, opened, clicked, bounced, and complained emails." parameters: - in: query name: from description: 'optional Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.' example: '2026-01-01' required: false schema: type: string description: 'optional Start date for the analytics period (ISO 8601 format). Defaults to 30 days ago.' example: '2026-01-01' - in: query name: to description: 'optional End date for the analytics period (ISO 8601 format). Defaults to today.' example: '2026-01-31' required: false schema: type: string description: 'optional End date for the analytics period (ISO 8601 format). Defaults to today.' example: '2026-01-31' responses: 200: description: '' content: application/json: schema: type: object example: success: true data: total_sent: 12500 total_delivered: 12300 total_opened: 5200 total_clicked: 1100 total_bounced: 150 total_complained: 5 delivery_rate: 98.4 open_rate: 42.3 click_rate: 8.9 bounce_rate: 1.2 properties: success: type: boolean example: true data: type: object properties: total_sent: type: integer example: 12500 total_delivered: type: integer example: 12300 total_opened: type: integer example: 5200 total_clicked: type: integer example: 1100 total_bounced: type: integer example: 150 total_complained: type: integer example: 5 delivery_rate: type: number example: 98.4 open_rate: type: number example: 42.3 click_rate: type: number example: 8.9 bounce_rate: type: number example: 1.2 tags: - Analytics '/api/v1/analytics/campaigns/{id}': get: summary: 'Campaign Analytics' operationId: campaignAnalytics description: "Retrieve detailed analytics for a specific email campaign, including delivery,\nengagement, and bounce statistics." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: campaign_id: 9a7f2e5c-1234-5678-90ab-cdef12345678 campaign_name: 'January Newsletter' total_sent: 5000 total_delivered: 4950 total_opened: 2100 total_clicked: 450 total_bounced: 50 total_complained: 2 open_rate: 42.4 click_rate: 9.1 bounce_rate: 1.0 unsubscribe_rate: 0.3 properties: success: type: boolean example: true data: type: object properties: campaign_id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 campaign_name: type: string example: 'January Newsletter' total_sent: type: integer example: 5000 total_delivered: type: integer example: 4950 total_opened: type: integer example: 2100 total_clicked: type: integer example: 450 total_bounced: type: integer example: 50 total_complained: type: integer example: 2 open_rate: type: number example: 42.4 click_rate: type: number example: 9.1 bounce_rate: type: number example: 1.0 unsubscribe_rate: type: number example: 0.3 404: description: 'Campaign Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Campaign not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Campaign not found' tags: - Analytics parameters: - in: path name: id description: 'The UUID of the campaign.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/analytics/campaigns/{id}/links': get: summary: 'Campaign Link Clicks' operationId: campaignLinkClicks description: "Retrieve click-tracking data for all links in a campaign, sorted by total clicks.\nShows which URLs were clicked and how many times." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: - url: 'https://exosend.com/pricing' total_clicks: 230 unique_clicks: 180 - url: 'https://exosend.com/docs' total_clicks: 145 unique_clicks: 120 properties: success: type: boolean example: true data: type: array example: - url: 'https://exosend.com/pricing' total_clicks: 230 unique_clicks: 180 - url: 'https://exosend.com/docs' total_clicks: 145 unique_clicks: 120 items: type: object properties: url: type: string example: 'https://exosend.com/pricing' total_clicks: type: integer example: 230 unique_clicks: type: integer example: 180 404: description: 'Campaign Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Campaign not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Campaign not found' tags: - Analytics parameters: - in: path name: id description: 'The UUID of the campaign.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/analytics/campaigns/{id}/devices': get: summary: 'Campaign Device Breakdown' operationId: campaignDeviceBreakdown description: "Retrieve device and email client breakdown for a campaign. Shows which devices,\noperating systems, and email clients recipients used to open the campaign emails." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: devices: - device: Desktop count: 1200 percentage: 57.1 - device: Mobile count: 750 percentage: 35.7 - device: Tablet count: 150 percentage: 7.1 clients: - client: Gmail count: 900 percentage: 42.9 - client: 'Apple Mail' count: 600 percentage: 28.6 - client: Outlook count: 350 percentage: 16.7 properties: success: type: boolean example: true data: type: object properties: devices: type: array example: - device: Desktop count: 1200 percentage: 57.1 - device: Mobile count: 750 percentage: 35.7 - device: Tablet count: 150 percentage: 7.1 items: type: object properties: device: type: string example: Desktop count: type: integer example: 1200 percentage: type: number example: 57.1 clients: type: array example: - client: Gmail count: 900 percentage: 42.9 - client: 'Apple Mail' count: 600 percentage: 28.6 - client: Outlook count: 350 percentage: 16.7 items: type: object properties: client: type: string example: Gmail count: type: integer example: 900 percentage: type: number example: 42.9 404: description: 'Campaign Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Campaign not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Campaign not found' tags: - Analytics parameters: - in: path name: id description: 'The UUID of the campaign.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/analytics/emails/{id}/events': get: summary: 'Email Event Timeline' operationId: emailEventTimeline description: "Retrieve the full event timeline for a specific email, including delivery,\nopen, click, bounce, and complaint events in chronological order." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: - id: 9b8f3e6d-2345-6789-01bc-def123456789 email_id: 9a7f2e5c-1234-5678-90ab-cdef12345678 event_type: delivered occurred_at: '2026-01-15T10:30:05+00:00' metadata: smtp_code: 250 - id: 9c9f4f7e-3456-7890-12cd-ef1234567890 email_id: 9a7f2e5c-1234-5678-90ab-cdef12345678 event_type: opened occurred_at: '2026-01-15T11:00:00+00:00' metadata: user_agent: Mozilla/5.0 ip: 192.168.1.1 - id: 9d0f5g8f-4567-8901-23de-f12345678901 email_id: 9a7f2e5c-1234-5678-90ab-cdef12345678 event_type: clicked occurred_at: '2026-01-15T11:05:30+00:00' metadata: url: 'https://exosend.com/pricing' user_agent: Mozilla/5.0 properties: success: type: boolean example: true data: type: array example: - id: 9b8f3e6d-2345-6789-01bc-def123456789 email_id: 9a7f2e5c-1234-5678-90ab-cdef12345678 event_type: delivered occurred_at: '2026-01-15T10:30:05+00:00' metadata: smtp_code: 250 - id: 9c9f4f7e-3456-7890-12cd-ef1234567890 email_id: 9a7f2e5c-1234-5678-90ab-cdef12345678 event_type: opened occurred_at: '2026-01-15T11:00:00+00:00' metadata: user_agent: Mozilla/5.0 ip: 192.168.1.1 - id: 9d0f5g8f-4567-8901-23de-f12345678901 email_id: 9a7f2e5c-1234-5678-90ab-cdef12345678 event_type: clicked occurred_at: '2026-01-15T11:05:30+00:00' metadata: url: 'https://exosend.com/pricing' user_agent: Mozilla/5.0 items: type: object properties: id: type: string example: 9b8f3e6d-2345-6789-01bc-def123456789 email_id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 event_type: type: string example: delivered occurred_at: type: string example: '2026-01-15T10:30:05+00:00' metadata: type: object properties: smtp_code: type: integer example: 250 404: description: 'Email Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Email not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Email not found' tags: - Analytics parameters: - in: path name: id description: 'The UUID of the email.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string /api/v1/suppressions: get: summary: 'List Suppressions' operationId: listSuppressions description: "Retrieve a paginated list of all suppressed email addresses for your organization.\nOptionally filter by suppression reason." parameters: - in: query name: reason description: 'Filter by suppression reason. Options: hard_bounce, soft_bounce, complaint, unsubscribed, manual, list_import.' example: hard_bounce required: false schema: type: string description: 'Filter by suppression reason. Options: hard_bounce, soft_bounce, complaint, unsubscribed, manual, list_import.' example: hard_bounce - in: query name: per_page description: 'Number of results per page (default 25).' example: 50 required: false schema: type: integer description: 'Number of results per page (default 25).' example: 50 responses: 200: description: '' content: application/json: schema: type: object example: success: true data: data: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 email: bounced@example.com reason: hard_bounce details: '550 Mailbox not found' created_at: '2026-01-10T10:00:00+00:00' - id: 9b8f3e6d-2345-6789-01bc-def123456789 email: unsubscribed@example.com reason: unsubscribed details: null created_at: '2026-01-12T15:30:00+00:00' pagination: total: 25 per_page: 25 current_page: 1 last_page: 1 properties: success: type: boolean example: true data: type: object properties: data: type: array example: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 email: bounced@example.com reason: hard_bounce details: '550 Mailbox not found' created_at: '2026-01-10T10:00:00+00:00' - id: 9b8f3e6d-2345-6789-01bc-def123456789 email: unsubscribed@example.com reason: unsubscribed details: null created_at: '2026-01-12T15:30:00+00:00' items: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 email: type: string example: bounced@example.com reason: type: string example: hard_bounce details: type: string example: '550 Mailbox not found' created_at: type: string example: '2026-01-10T10:00:00+00:00' pagination: type: object properties: total: type: integer example: 25 per_page: type: integer example: 25 current_page: type: integer example: 1 last_page: type: integer example: 1 tags: - 'Suppression List' post: summary: 'Suppress Email Address' operationId: suppressEmailAddress description: "Add an email address to your organization's suppression list. Suppressed addresses\nwill not receive any future emails. If the address is already suppressed, the\nexisting suppression record is returned." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 email: user@example.com reason: manual details: 'Requested removal via support ticket #1234' created_at: '2026-01-15T10:00:00+00:00' message: 'Email suppressed.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 email: type: string example: user@example.com reason: type: string example: manual details: type: string example: 'Requested removal via support ticket #1234' created_at: type: string example: '2026-01-15T10:00:00+00:00' message: type: string example: 'Email suppressed.' 422: description: 'Validation Error' content: application/json: schema: type: object example: success: false error: code: VALIDATION_ERROR message: 'The given data was invalid' details: email: - 'The email field is required.' reason: - 'The selected reason is invalid.' properties: success: type: boolean example: false error: type: object properties: code: type: string example: VALIDATION_ERROR message: type: string example: 'The given data was invalid' details: type: object properties: email: type: array example: - 'The email field is required.' items: type: string reason: type: array example: - 'The selected reason is invalid.' items: type: string tags: - 'Suppression List' requestBody: required: true content: application/json: schema: type: object properties: email: type: string description: 'The email address to suppress.' example: user@example.com reason: type: string description: 'optional Reason for suppression. Options: hard_bounce, soft_bounce, complaint, unsubscribed, manual, list_import. Default: manual.' example: manual details: type: string description: 'optional Additional details about the suppression.' example: 'Requested removal via support ticket #1234' nullable: true required: - email '/api/v1/suppressions/{email}': delete: summary: 'Unsuppress Email Address' operationId: unsuppressEmailAddress description: "Remove an email address from your organization's suppression list, allowing\nfuture emails to be delivered to this address again." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: null message: 'Email unsuppressed.' properties: success: type: boolean example: true data: type: string example: null message: type: string example: 'Email unsuppressed.' 404: description: 'Suppression Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Suppression not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Suppression not found' tags: - 'Suppression List' parameters: - in: path name: email description: 'The email address to unsuppress.' example: user@example.com required: true schema: type: string '/api/v1/suppressions/check/{email}': get: summary: 'Check Suppression Status' operationId: checkSuppressionStatus description: "Check whether a specific email address is on your organization's suppression list.\nReturns a boolean indicating the suppression status." parameters: [] responses: 200: description: '' content: application/json: schema: oneOf: - description: Suppressed type: object example: success: true data: email: user@example.com is_suppressed: true properties: success: type: boolean example: true data: type: object properties: email: type: string example: user@example.com is_suppressed: type: boolean example: true - description: 'Not Suppressed' type: object example: success: true data: email: active@example.com is_suppressed: false properties: success: type: boolean example: true data: type: object properties: email: type: string example: active@example.com is_suppressed: type: boolean example: false tags: - 'Suppression List' parameters: - in: path name: email description: 'The email address to check.' example: user@example.com required: true schema: type: string /api/v1/balance: get: summary: 'Get Credit Balance' operationId: getCreditBalance description: "Retrieve your organization's current credit balance. Credits are used to send SMS messages,\nwith costs varying by destination country and message length." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: balance: 99.965 currency: EUR organization: ExoClass properties: success: type: boolean example: true data: type: object properties: balance: type: number example: 99.965 currency: type: string example: EUR organization: type: string example: ExoClass tags: - 'Account Management' /api/v1/sender-ids: get: summary: 'List Sender IDs' operationId: listSenderIDs description: "Retrieve all sender IDs for your organization with optional status filtering.\nBy default, only verified sender IDs are returned." parameters: - in: query name: status description: 'Filter by status. Options: verified, pending, approved, rejected, all. Default: verified.' example: verified required: false schema: type: string description: 'Filter by status. Options: verified, pending, approved, rejected, all. Default: verified.' example: verified responses: 200: description: '' content: application/json: schema: type: object example: success: true data: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 sender_id: EXOCLASS admin_status: approved provider_status: verified is_verified: true is_default: true rejection_reason: null message_count: 42 created_at: '2026-01-01T10:00:00+00:00' approved_at: '2026-01-01T12:00:00+00:00' verified_at: '2026-01-01T14:00:00+00:00' - id: 9a7f2e5c-1234-5678-90ab-cdef12345679 sender_id: EXOSMS admin_status: approved provider_status: verified is_verified: true is_default: false rejection_reason: null message_count: 15 created_at: '2026-01-02T10:00:00+00:00' approved_at: '2026-01-02T11:00:00+00:00' verified_at: '2026-01-02T13:00:00+00:00' properties: success: type: boolean example: true data: type: array example: - id: 9a7f2e5c-1234-5678-90ab-cdef12345678 sender_id: EXOCLASS admin_status: approved provider_status: verified is_verified: true is_default: true rejection_reason: null message_count: 42 created_at: '2026-01-01T10:00:00+00:00' approved_at: '2026-01-01T12:00:00+00:00' verified_at: '2026-01-01T14:00:00+00:00' - id: 9a7f2e5c-1234-5678-90ab-cdef12345679 sender_id: EXOSMS admin_status: approved provider_status: verified is_verified: true is_default: false rejection_reason: null message_count: 15 created_at: '2026-01-02T10:00:00+00:00' approved_at: '2026-01-02T11:00:00+00:00' verified_at: '2026-01-02T13:00:00+00:00' items: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 sender_id: type: string example: EXOCLASS admin_status: type: string example: approved provider_status: type: string example: verified is_verified: type: boolean example: true is_default: type: boolean example: true rejection_reason: type: string example: null message_count: type: integer example: 42 created_at: type: string example: '2026-01-01T10:00:00+00:00' approved_at: type: string example: '2026-01-01T12:00:00+00:00' verified_at: type: string example: '2026-01-01T14:00:00+00:00' tags: - 'Sender ID Management' post: summary: 'Request New Sender ID' operationId: requestNewSenderID description: "Submit a new sender ID request for approval. The sender ID will be created in pending status\nand must go through a two-stage approval process:\n1. Admin approval (manual review by ExoSend administrators)\n2. Provider verification (automatic verification with SMS provider)\n\nOnly after both stages can the sender ID be used for sending messages." parameters: [] responses: 201: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 sender_id: EXOCLASS admin_status: pending provider_status: pending is_verified: false is_default: false created_at: '2026-01-03T14:30:00+00:00' message: 'Sender ID request submitted. Awaiting approval from administrator.' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 sender_id: type: string example: EXOCLASS admin_status: type: string example: pending provider_status: type: string example: pending is_verified: type: boolean example: false is_default: type: boolean example: false created_at: type: string example: '2026-01-03T14:30:00+00:00' message: type: string example: 'Sender ID request submitted. Awaiting approval from administrator.' 422: description: 'Validation Error' content: application/json: schema: type: object example: success: false error: code: VALIDATION_ERROR message: 'The given data was invalid' details: sender_id: - 'The sender id must be between 3 and 11 characters.' properties: success: type: boolean example: false error: type: object properties: code: type: string example: VALIDATION_ERROR message: type: string example: 'The given data was invalid' details: type: object properties: sender_id: type: array example: - 'The sender id must be between 3 and 11 characters.' items: type: string tags: - 'Sender ID Management' requestBody: required: true content: application/json: schema: type: object properties: sender_id: type: string description: 'The sender ID (3-11 alphanumeric characters). Will be converted to uppercase.' example: EXOCLASS required: - sender_id '/api/v1/sender-ids/{id}/default': patch: summary: 'Set Default Sender ID' operationId: setDefaultSenderID description: "Set a verified sender ID as the default for your organization.\nOnly verified sender IDs can be set as default. Automatically unsets\nany previously set default sender ID." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: id: 9a7f2e5c-1234-5678-90ab-cdef12345678 sender_id: EXOCLASS is_verified: true is_default: true message: 'Sender ID set as default' properties: success: type: boolean example: true data: type: object properties: id: type: string example: 9a7f2e5c-1234-5678-90ab-cdef12345678 sender_id: type: string example: EXOCLASS is_verified: type: boolean example: true is_default: type: boolean example: true message: type: string example: 'Sender ID set as default' 404: description: 'Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Sender ID not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Sender ID not found' 422: description: 'Not Verified' content: application/json: schema: type: object example: success: false error: code: NOT_VERIFIED message: 'Only verified sender IDs can be set as default. Current status: pending approval.' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_VERIFIED message: type: string example: 'Only verified sender IDs can be set as default. Current status: pending approval.' tags: - 'Sender ID Management' parameters: - in: path name: id description: 'The UUID of the sender ID.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string '/api/v1/sender-ids/{id}': delete: summary: 'Delete Sender ID' operationId: deleteSenderID description: "Delete a sender ID from your organization. Sender IDs with existing messages\nor set as default cannot be deleted." parameters: [] responses: 200: description: '' content: application/json: schema: type: object example: success: true data: null message: 'Sender ID deleted successfully' properties: success: type: boolean example: true data: type: string example: null message: type: string example: 'Sender ID deleted successfully' 404: description: 'Not Found' content: application/json: schema: type: object example: success: false error: code: NOT_FOUND message: 'Sender ID not found' properties: success: type: boolean example: false error: type: object properties: code: type: string example: NOT_FOUND message: type: string example: 'Sender ID not found' 422: description: '' content: application/json: schema: oneOf: - description: 'Is Default' type: object example: success: false error: code: IS_DEFAULT message: 'Cannot delete the default sender ID. Set another as default first.' properties: success: type: boolean example: false error: type: object properties: code: type: string example: IS_DEFAULT message: type: string example: 'Cannot delete the default sender ID. Set another as default first.' - description: 'Has Messages' type: object example: success: false error: code: HAS_MESSAGES message: 'Cannot delete sender ID with existing messages' details: message_count: 42 properties: success: type: boolean example: false error: type: object properties: code: type: string example: HAS_MESSAGES message: type: string example: 'Cannot delete sender ID with existing messages' details: type: object properties: message_count: type: integer example: 42 tags: - 'Sender ID Management' parameters: - in: path name: id description: 'The UUID of the sender ID.' example: 9a7f2e5c-1234-5678-90ab-cdef12345678 required: true schema: type: string /api/v1/sms/campaigns: post: summary: 'Create SMS Campaign' operationId: createSMSCampaign description: "Create a new SMS campaign. The campaign will be in draft status unless scheduled_at is provided,\nin which case it will be set to scheduled status." parameters: [] responses: { } tags: - 'SMS Campaigns' requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: 'Must not be greater than 255 characters.' example: b message_content: type: string description: 'Must not be greater than 1600 characters.' example: 'n' sender_id_id: type: string description: 'Must be a valid UUID.' example: 6b72fe4a-5b40-307c-bc24-f79acf9a1bb9 target_list_ids: type: array description: 'Must be a valid UUID.' example: - 977e5426-8d13-3824-86aa-b092f8ae52c5 items: type: string contact_ids: type: array description: 'Must be a valid UUID.' example: - d6fa562b-acd5-35ff-babb-d11194d3737b items: type: string manual_phones: type: array description: 'Must match the regex /^\+[1-9]\d{1,14}$/.' example: - '+36' items: type: string scheduled_at: type: string description: 'Must be a valid date. Must be a date after 2026-03-04 23:30:31.' example: '2052-03-28' nullable: true timezone: type: string description: 'This field is required when scheduled_at is present.' example: Asia/Yekaterinburg nullable: true batch_size: type: integer description: 'Must be at least 1. Must not be greater than 50000.' example: 22 nullable: true send_rate_per_minute: type: integer description: 'Must be at least 1. Must not be greater than 10000.' example: 7 nullable: true required: - name - message_content - sender_id_id get: summary: 'List SMS Campaigns' operationId: listSMSCampaigns description: 'Retrieve a paginated list of all SMS campaigns for your organization, sorted by newest first.' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - 'SMS Campaigns' '/api/v1/sms/campaigns/{smsCampaign_id}': get: summary: 'Get SMS Campaign Details' operationId: getSMSCampaignDetails description: 'Retrieve full details of a specific SMS campaign including refreshed stats.' parameters: [] responses: 401: description: '' content: application/json: schema: type: object example: message: Unauthenticated. properties: message: type: string example: Unauthenticated. tags: - 'SMS Campaigns' parameters: - in: path name: smsCampaign_id description: 'The ID of the smsCampaign.' example: 019ca407-ed12-71fa-8020-ed1b6fefc081 required: true schema: type: string '/api/v1/sms/campaigns/{smsCampaign_id}/send': post: summary: 'Send SMS Campaign' operationId: sendSMSCampaign description: 'Start sending a draft or scheduled campaign immediately.' parameters: [] responses: { } tags: - 'SMS Campaigns' parameters: - in: path name: smsCampaign_id description: 'The ID of the smsCampaign.' example: 019ca407-ed12-71fa-8020-ed1b6fefc081 required: true schema: type: string '/api/v1/sms/campaigns/{smsCampaign_id}/pause': post: summary: 'Pause SMS Campaign' operationId: pauseSMSCampaign description: 'Pause a campaign that is currently sending.' parameters: [] responses: { } tags: - 'SMS Campaigns' parameters: - in: path name: smsCampaign_id description: 'The ID of the smsCampaign.' example: 019ca407-ed12-71fa-8020-ed1b6fefc081 required: true schema: type: string '/api/v1/sms/campaigns/{smsCampaign_id}/cancel': post: summary: 'Cancel SMS Campaign' operationId: cancelSMSCampaign description: 'Cancel a campaign that is sending, paused, or scheduled.' parameters: [] responses: { } tags: - 'SMS Campaigns' parameters: - in: path name: smsCampaign_id description: 'The ID of the smsCampaign.' example: 019ca407-ed12-71fa-8020-ed1b6fefc081 required: true schema: type: string