{
  "openapi": "3.1.0",
  "info": {
    "title": "Envoke Message API",
    "version": "2.0.0",
    "description": "Read, create, update, delete, send, and cancel the email messages in an Envoke account. A message is one email campaign: its settings (name, group, sender, reply-to, subject, language, recipients, send time) and its HTML content. All endpoints require HTTP Basic Auth and exchange JSON. The API returns no delivery, open, or click statistics. Points a client must know: (1) The Message API answers on `/v2` only; `/v1/messages` is not an API. (2) Only a `Draft` message can be updated or sent; any other status gives `Only 'Draft' messages can be updated, this message has the status: '{Status}'`. To change a `Scheduled` message, cancel it (back to `Draft`), update it, and send it again. (3) Sending is asynchronous. A send request checks the message, estimates its recipients, and schedules it; the response is always `Message scheduled for send`, even for an immediate send, and its recipient counts are an estimate taken at the time of the request. Envoke's sending process then moves the message through `Queuing` and `Sending` to `Sent`; read the message to follow it. (4) There are two error envelopes. Validation errors return `result_text` `Validation failed, see 'result_data' -> 'errors'` with `result_data.errors` as an array of strings; when a lookup field (`group`, `sender`, `reply_to`, `language`, `recipient_language`, `recipient_subscription`, `recipient_tag`, `recipient_segment`) has an unknown value, `result_data.allowed_values.{field}` also lists the valid options keyed by ID, when the account has 50 or fewer of them. Plain errors (`Invalid JSON`, `Invalid request`, the ID mismatch on update, send, and cancel, `Message content error`, the delete errors) put the message in `result_text` with `result_data: []`. The zero-recipient send result is `success: false` with the counts in `result_data` and no `errors` key. (5) `limit=1` on the list endpoint returns the matching message as a bare object, not an array of one, and `result_type=kvp` is ignored with it. (6) `id` is a string everywhere except `result_data.id` of `POST /messages/send` (create and send), which is an integer. (7) Delete is soft: the message moves to the Deleted folder in Envoke, where it can be restored only in the Envoke app; the API stops returning it at once. (8) All times are wall-clock times in the account time zone, `Y-m-d H:i:s` with no offset; an offset or `Z` in a written `send_time` is dropped. All error responses use HTTP 400, except authentication and account errors: invalid credentials return 401 with `result_text` `Invalid credentials` and `result_data: []`; a valid key on a closed or inactive account returns 403 with the plain-text body `This account has been closed.` or `This account is not active.`; exceeding the account's API limit returns 429 with `success: false` and a `result_text` naming the limit."
  },
  "servers": [
    {
      "url": "https://e1.envoke.com/v2",
      "description": "Production"
    }
  ],
  "security": [
    {
      "basicAuth": []
    }
  ],
  "tags": [
    {
      "name": "Messages",
      "description": "Read, create, update, and delete messages."
    },
    {
      "name": "Sending",
      "description": "Send a draft, create and send in one request, and cancel a scheduled send."
    }
  ],
  "components": {
    "securitySchemes": {
      "basicAuth": {
        "type": "http",
        "scheme": "basic",
        "description": "HTTP Basic Auth. Use your API ID as the username and API Key as the password. See https://developers.envoke.com/guides/authentication/."
      }
    },
    "parameters": {
      "MessageId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "The Envoke message ID.",
        "schema": {
          "type": "integer",
          "minimum": 1
        },
        "example": 2001
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "description": "Maximum number of messages to return. Default 10, hard cap 100. When `fields` or `result_type=kvp` is also set, the default becomes 10,000 and a value above 100 is honoured as-is. `limit=1` returns a single message as a bare object instead of an array (or `[]` when nothing matches), and `result_type=kvp` has no effect with it. A value that is not a whole number gives `Invalid 'limit': '{value}'`.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 10
        }
      },
      "Skip": {
        "name": "skip",
        "in": "query",
        "description": "Number of messages to skip. Use with `limit` and `sort` to paginate. A value that is not a whole number gives `Invalid 'skip': '{value}'`.",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "default": 0
        }
      },
      "Fields": {
        "name": "fields",
        "in": "query",
        "description": "Comma-separated list of fields to include in each result, e.g. `id,name,status`. Use names from the Message schema. On the list endpoint, setting it raises the default limit to 10,000 and lifts the 100 cap. On a single GET with `include_html`, list `html` to keep the HTML.",
        "schema": {
          "type": "string"
        },
        "example": "id,name,status,send_time"
      },
      "ResultType": {
        "name": "result_type",
        "in": "query",
        "description": "Set to `kvp` to receive an object of message ID to name (`{ \"2001\": \"Spring Season Announcement\" }`) instead of an array. Raises the default limit to 10,000 and lifts the 100 cap. Has no effect when `limit=1` or on a single GET.",
        "schema": {
          "type": "string",
          "enum": [
            "kvp"
          ]
        }
      },
      "IncludeHtml": {
        "name": "include_html",
        "in": "query",
        "description": "Single GET only. `1` adds the message HTML as the last key, `html`. With `0` or without the parameter, the HTML is left out. When `fields` is also set, list `html` in it to keep the HTML. Ignored on the list endpoint, which never returns HTML.",
        "schema": {
          "type": "integer",
          "enum": [
            0,
            1
          ]
        },
        "example": 1
      }
    },
    "schemas": {
      "Message": {
        "type": "object",
        "description": "A message as returned by GET and by the create, update, and cancel responses. Every readable field is present unless `fields` selects some. An unset text value is `\"\"`, except `sender` and `reply_to`, which are `null` when no identity is set. `html` is present only on a single GET with `include_html=1`, as the last key.",
        "required": [
          "id",
          "external_id",
          "preview_url",
          "create_time",
          "modified_time",
          "name",
          "group",
          "status",
          "send_time",
          "sender",
          "reply_to",
          "subject",
          "language",
          "recipient_subscription",
          "recipient_tag",
          "recipient_segment",
          "recipient_language",
          "publish_archive",
          "utm_tag_links"
        ],
        "properties": {
          "id": {
            "type": "string",
            "readOnly": true,
            "description": "Envoke message ID, assigned on creation. Returned as a string (the one exception is `result_data.id` of `POST /messages/send`). Ignored on write.",
            "examples": [
              "2001"
            ]
          },
          "external_id": {
            "type": "string",
            "readOnly": true,
            "minLength": 32,
            "maxLength": 32,
            "description": "32-character externally visible identifier used to build `preview_url`. Not the same as `id`. Ignored on write.",
            "examples": [
              "3e8b1f6c9a2d4e7b0c5f8a1d3e6b9c2f"
            ]
          },
          "preview_url": {
            "type": "string",
            "format": "uri",
            "readOnly": true,
            "description": "Public URL that shows the message in a browser without logging in: `https://{domain}/m/{account_external_id}/m/{external_id}`, where `{domain}` is the account's custom domain if it has one and otherwise Envoke's message domain (not the API host). Ignored on write.",
            "examples": [
              "https://engage.envoke.com/m/a7c3e9f1b5d2/m/3e8b1f6c9a2d4e7b0c5f8a1d3e6b9c2f"
            ]
          },
          "create_time": {
            "type": "string",
            "readOnly": true,
            "description": "When the message was created, `Y-m-d H:i:s` in the account time zone with no offset. Ignored on write. Sortable, not filterable.",
            "examples": [
              "2026-10-06 10:15:42"
            ]
          },
          "modified_time": {
            "type": "string",
            "readOnly": true,
            "description": "When the message was last changed, `Y-m-d H:i:s` in the account time zone with no offset. Changes on every successful update (even one that changes no value), send, and cancel. The list is sorted by this field, newest first, by default. Ignored on write. Sortable, not filterable.",
            "examples": [
              "2026-10-06 11:02:08"
            ]
          },
          "name": {
            "type": "string",
            "minLength": 3,
            "description": "Internal name shown in the Envoke message list; recipients do not see it. At least 3 characters (`The field 'name' must be at least 3 characters long`); a longer name is cut to its first 300 characters without an error. Generated as `API Message {Y-m-d H:i:s}` from Envoke's server clock when omitted on create.",
            "examples": [
              "Spring Season Announcement"
            ]
          },
          "group": {
            "type": "string",
            "description": "Name of the message group (folder), or `\"\"` when the message has no group.",
            "examples": [
              "Productions",
              ""
            ]
          },
          "status": {
            "type": "string",
            "readOnly": true,
            "enum": [
              "Draft",
              "Scheduled",
              "AB test in progress",
              "Queuing",
              "Sending",
              "Transactional",
              "Nurture",
              "Sent"
            ],
            "description": "Current status, returned by name. Codes: `Draft` 0, `Scheduled` 1, `AB test in progress` 2, `Queuing` 4, `Sending` 5, `Transactional` 7, `Nurture` 8, `Sent` 9. Changed only by sending and cancelling; ignored on write. Only a `Draft` can be updated or sent. Filterable by name or code.",
            "examples": [
              "Draft"
            ]
          },
          "send_time": {
            "type": "string",
            "description": "Scheduled send time, `Y-m-d H:i:s` in the account time zone, or `\"\"` when no time is set. Setting it on a draft does not schedule the message. Once set it cannot be cleared, and it survives a cancel.",
            "examples": [
              "2026-10-20 09:00:00",
              ""
            ]
          },
          "sender": {
            "type": [
              "string",
              "null"
            ],
            "description": "Sender identity as `Name <email>`, or `null` when no identity is set. Defaults to the account's default sender on create.",
            "examples": [
              "Box Office <boxoffice@example.org>"
            ]
          },
          "reply_to": {
            "type": [
              "string",
              "null"
            ],
            "description": "Reply-to identity as `Name <email>`, or `null` when no identity is set (the default). When not set, replies go to the sender address.",
            "examples": [
              "Front of House <frontofhouse@example.org>",
              null
            ]
          },
          "subject": {
            "type": "string",
            "description": "Subject line, or `\"\"`. Required to send.",
            "examples": [
              "Our spring season is here"
            ]
          },
          "language": {
            "type": "string",
            "enum": [
              "en",
              "fr"
            ],
            "description": "Language the message is written in. Defaults to `en`.",
            "examples": [
              "en"
            ]
          },
          "recipient_subscription": {
            "type": "string",
            "description": "Name of the subscription the message is sent to, or `\"\"`.",
            "examples": [
              "Season News",
              ""
            ]
          },
          "recipient_tag": {
            "type": "string",
            "description": "Name of the tag the message is sent to, or `\"\"`. `\"\"` when the message is sent to a subscription alone.",
            "examples": [
              "Cast Call",
              ""
            ]
          },
          "recipient_segment": {
            "type": "string",
            "description": "Name of the segment the message is sent to, or `\"\"`.",
            "examples": [
              "Lapsed Subscribers",
              ""
            ]
          },
          "recipient_language": {
            "type": "string",
            "enum": [
              "en",
              "fr",
              ""
            ],
            "description": "Only contacts with this language are sent the message; `\"\"` for all languages. When it matches the account's default language, contacts with no language set are included too.",
            "examples": [
              "",
              "fr"
            ]
          },
          "publish_archive": {
            "type": "boolean",
            "description": "Whether the message is published to the account's public message archive. Defaults to `false`.",
            "examples": [
              true
            ]
          },
          "utm_tag_links": {
            "type": "boolean",
            "description": "Whether UTM tracking parameters are added to the links in the message. Defaults on create to the account's link analytics preference.",
            "examples": [
              true
            ]
          },
          "html": {
            "type": "string",
            "description": "The message HTML. Present only on `GET /messages/{id}?include_html=1`, as the last key.",
            "examples": [
              "<html><body><h1>The Pirates of Penzance</h1><p>Curtain at 7:30 p.m.</p></body></html>"
            ]
          }
        }
      },
      "Replace": {
        "type": "object",
        "description": "Placeholder text to replace in the message HTML once, when the message is saved; the HTML is stored with the replacements made and nothing is replaced at send time. Every key must start with `{` and end with `}`; every value must be a string and is inserted as is, without escaping, so it can contain HTML. Keys are applied in the order sent, so a later key can match text inserted by an earlier value. A key that does not appear in the HTML is ignored, and `{}` is ignored. A key may not contain `{@pref-`, `{@consent-url`, or `{@consent-prefpage-url`. Any other shape (a list, a non-string value, a key without braces, a protected key) fails with `Invalid 'replace' data`. On create it applies to `html` or to the HTML copied through `template_id`; on update it needs `html` in the same request (`The field 'html' is required for content 'replace' data`). Envoke merge tags such as `{@first_name}` are not protected; avoid them as keys. Never returned.",
        "propertyNames": {
          "pattern": "^\\{.+\\}$"
        },
        "additionalProperties": {
          "type": "string"
        },
        "examples": [
          {
            "{SHOW_TITLE}": "The Pirates of Penzance",
            "{CURTAIN_TIME}": "7:30 p.m."
          }
        ]
      },
      "MessageWritable": {
        "type": "object",
        "description": "Writable message fields shared by create, update, send, and cancel bodies. `id` (on create), `external_id`, `preview_url`, `create_time`, `modified_time`, and `status` are read-only and silently ignored. Any other key gives `'{key}' is not a valid field`. Lookup fields (`group`, `sender`, `reply_to`, `language`, `recipient_*`) are cleared with `\"\"` or the string `\"0\"`; a JSON number `0` fails as an invalid value (e.g. `Invalid 'group': '0'`).",
        "properties": {
          "name": {
            "type": "string",
            "minLength": 3,
            "description": "At least 3 characters: `The field 'name' must be at least 3 characters long`. Cannot be cleared.",
            "examples": [
              "Spring Season Announcement"
            ]
          },
          "group": {
            "type": [
              "string",
              "integer"
            ],
            "description": "Name or ID of an existing message group. Names are matched ignoring case, with a space and an underscore treated as the same. Groups are not created automatically; template and contact groups are not accepted. Unknown: `Invalid 'group': '{value}'` with `allowed_values.group`. `\"\"` or `\"0\"` removes the group.",
            "examples": [
              "Productions",
              31
            ]
          },
          "subject": {
            "type": "string",
            "minLength": 6,
            "description": "At least 6 characters: `The field 'subject' must be at least 6 characters long`; a longer subject is cut to its first 360 characters without an error. Required to send. Cannot be cleared.",
            "examples": [
              "Our spring season is here"
            ]
          },
          "sender": {
            "type": [
              "string",
              "integer"
            ],
            "description": "A sender identity, resolved in this order: an identity ID (`412`); `Name <email>` matching an identity, ignoring case and treating a space and an underscore as the same; `\"\"` or `\"0\"` clears it (on update, with no fallback to the default, and the message cannot then be sent: `The field 'sender' must be set`); an email address, matched exactly including case against the first identity with that address (`No sender identity found for email: '{value}'`); anything else gives `Invalid 'sender': '{value}'`. Errors include `allowed_values.sender` keyed by ID as `Name <email>`. Defaults on create (or when sent as `\"\"`) to the account's default sender.",
            "examples": [
              "boxoffice@example.org",
              412,
              "Box Office <boxoffice@example.org>"
            ]
          },
          "reply_to": {
            "type": [
              "string",
              "integer"
            ],
            "description": "Same formats and resolution as `sender`; errors read `Invalid 'reply_to': '{value}'` and `No reply_to identity found for email: '{value}'`. No default. `\"\"` or `\"0\"` clears it.",
            "examples": [
              413
            ]
          },
          "language": {
            "type": [
              "string",
              "integer"
            ],
            "description": "`en` or `fr` (case-insensitive), or the IDs `1` (English) or `3` (French). `\"\"` or `\"0\"` sets `en`. Otherwise `Invalid 'language': '{value}'` with `allowed_values.language` `{\"1\":\"en\",\"3\":\"fr\"}`.",
            "examples": [
              "fr"
            ]
          },
          "recipient_subscription": {
            "type": [
              "string",
              "integer"
            ],
            "description": "Name or ID of a subscription, including the account's mandatory consent subscription (which sends the message as a mandatory, consent-exempt message, with the subscription's description as the stated reason). With a tag or segment, the subscription narrows it: only contacts in the tag or segment and subscribed are sent the message. Unknown: `Invalid 'recipient_subscription': '{value}'` with `allowed_values` when the account has 50 or fewer. On update, sending any one of the three recipient fields clears the other two unless they are in the same request; `{\"recipient_subscription\": \"\"}` clears all three.",
            "examples": [
              "Season News"
            ]
          },
          "recipient_tag": {
            "type": [
              "string",
              "integer"
            ],
            "description": "Name or ID of a tag. Not with `recipient_segment`: `Only one of the fields 'recipient_tag' or 'recipient_segment' may be provided`. Unknown: `Invalid 'recipient_tag': '{value}'`. Subject to the update rule described under `recipient_subscription`.",
            "examples": [
              "Cast Call"
            ]
          },
          "recipient_segment": {
            "type": [
              "string",
              "integer"
            ],
            "description": "Name or ID of a segment. Segments cannot be created through any API. Not with `recipient_tag`. Unknown: `Invalid 'recipient_segment': '{value}'`. Subject to the update rule described under `recipient_subscription`.",
            "examples": [
              "Lapsed Subscribers"
            ]
          },
          "recipient_language": {
            "type": [
              "string",
              "integer"
            ],
            "description": "`en`, `fr`, `1`, or `3`: send only to contacts with that language. `\"\"` or `\"0\"` clears it (all languages). Never cleared by the recipient update rule. Otherwise `Invalid 'recipient_language': '{value}'`.",
            "examples": [
              "en"
            ]
          },
          "publish_archive": {
            "type": [
              "boolean",
              "string",
              "integer"
            ],
            "description": "`true`, `\"true\"`, `1`, `\"1\"`, `\"yes\"`, or `\"on\"` publishes the message to the web archive; anything else is `false`. Defaults to `false`.",
            "examples": [
              true
            ]
          },
          "utm_tag_links": {
            "type": [
              "boolean",
              "string",
              "integer"
            ],
            "description": "Accepted values as for `publish_archive`. Defaults on create to `true` if link analytics is on in the account preferences, otherwise `false`.",
            "examples": [
              true
            ]
          },
          "send_time": {
            "type": "string",
            "description": "A future date and time, read as account-local wall-clock time, e.g. `2026-10-20 09:00:00`. An offset or `Z` is dropped and the remaining time is still read in the account time zone. Stored only: it does not schedule the message; it is used when the message is sent. A time in the past gives `The field 'send_time' must be in the future`; a value that is not a date gives `Invalid 'send_time': '{value}'`; `\"\"` or `null` is read as the current time and fails as not in the future, so `send_time` cannot be cleared.",
            "examples": [
              "2026-10-20 09:00:00"
            ]
          },
          "html": {
            "type": "string",
            "maxLength": 204800,
            "description": "The complete message HTML, stored as sent without checks. Over 204,800 bytes fails with `The field 'html' may not be longer than 200kB`; the size is checked before `replace` is applied. `\"\"` counts as not sent, so content cannot be blanked. On update it replaces the whole content. HTML containing Envoke's `<meta name=\"TEMPLATE_VERSION\"` tag opens in the drag-and-drop editor in Envoke; other HTML opens in the HTML editor.",
            "examples": [
              "<html><body><h1>Spring Season</h1><p>Four shows, one season pass.</p></body></html>"
            ]
          },
          "replace": {
            "$ref": "#/components/schemas/Replace"
          }
        }
      },
      "MessageCreate": {
        "description": "Create body. One of `html` or `template_id` is required (`One of the fields 'template_id' or 'html' must be provided in order to create a message`); when both are sent, `html` is used and `template_id` is ignored. A message can be created without a subject, sender, or recipients; they are checked when it is sent. Checks run in order and the first six return a single error: empty body, `html` size, `template_id` not found, neither content field, tag and segment together, invalid `replace`; the field checks are then collected and returned together. Defaults: `name` `API Message {Y-m-d H:i:s}`, `status` `Draft`, `group` `\"\"`, `sender` the account default, `reply_to` `null`, `subject` `\"\"`, `language` `en`, recipient fields `\"\"`, `publish_archive` `false`, `utm_tag_links` from the account's link analytics preference, `send_time` `\"\"`.",
        "allOf": [
          {
            "$ref": "#/components/schemas/MessageWritable"
          },
          {
            "type": "object",
            "properties": {
              "template_id": {
                "type": [
                  "integer",
                  "string"
                ],
                "description": "ID of a template, or of another message in the account, whose HTML the new message copies; templates shared with the account can be used. A numeric string is accepted. Only the HTML (and which editor the content opens in) is copied, not the subject, sender, reply-to, language, group, or recipients; the copy is independent of the source. Not found: `Nothing found for 'template_id': {value}`. Create only; never returned.",
                "examples": [
                  1901
                ]
              }
            }
          }
        ],
        "anyOf": [
          {
            "required": [
              "html"
            ]
          },
          {
            "required": [
              "template_id"
            ]
          }
        ]
      },
      "MessageUpdate": {
        "description": "Update body: only the fields to change. Fields left out keep their values, except that the recipient fields are replaced as a set. `template_id` gives `The field 'template_id' is not valid for updating a message`. Checks run in order and each of the first six returns on its own: empty body, message not found, not a `Draft`, `template_id` sent, `html` size, tag and segment together; the field checks are then collected and returned together, before the `replace` checks.",
        "allOf": [
          {
            "$ref": "#/components/schemas/MessageWritable"
          },
          {
            "type": "object",
            "properties": {
              "id": {
                "type": [
                  "string",
                  "integer"
                ],
                "description": "Optional. If sent, it must match the ID in the URL (`\"2001\"` and `2001` count as the same); otherwise the request fails with the plain envelope `Provided 'id': {url id} does not match 'id' in request body ({body id})`.",
                "examples": [
                  "2002"
                ]
              }
            }
          }
        ]
      },
      "SendRequest": {
        "description": "Optional body for `POST /messages/{id}/send` and `POST /messages/{id}/cancel`: any update field. On send, the fields are saved first, exactly as a `PATCH` would, and stay saved even if the send then fails; most often the body is `send_time`. On cancel, they are applied after the message is returned to `Draft`; if one fails validation, the message is already a `Draft` but the response is the validation error. An empty body is accepted.",
        "$ref": "#/components/schemas/MessageUpdate"
      },
      "Recipients": {
        "type": "object",
        "description": "The recipient estimate, taken at the time of the request. The recipients are selected again when the send starts, so the actual number can differ. The counts overlap: one contact can be excluded for several reasons and is counted under each.",
        "required": [
          "total_selected",
          "emails_to_be_sent",
          "excluded_unsubscribed_contacts",
          "excluded_invalid_emails",
          "excluded_bounced_emails",
          "excluded_unverified_emails",
          "excluded_by_language"
        ],
        "properties": {
          "total_selected": {
            "type": "integer",
            "description": "Every contact in the tag, subscription, or segment (after a subscription narrows a tag or segment).",
            "examples": [
              412
            ]
          },
          "emails_to_be_sent": {
            "type": "integer",
            "description": "Contacts who pass every check; the estimated number of emails.",
            "examples": [
              377
            ]
          },
          "excluded_unsubscribed_contacts": {
            "type": "integer",
            "description": "Contacts whose consent does not allow sending, such as `Not Provided`, `Revoked`, or `Expired`. On a mandatory consent message, only contacts whose consent blocks all email (such as a spam report).",
            "examples": [
              29
            ]
          },
          "excluded_invalid_emails": {
            "type": "integer",
            "description": "Contacts whose email address is not valid.",
            "examples": [
              2
            ]
          },
          "excluded_bounced_emails": {
            "type": "integer",
            "description": "Contacts whose address has been marked undeliverable.",
            "examples": [
              4
            ]
          },
          "excluded_unverified_emails": {
            "type": "integer",
            "description": "Contacts who have not confirmed their address; only when the account requires email confirmation (double opt-in), otherwise `0`.",
            "examples": [
              0
            ]
          },
          "excluded_by_language": {
            "type": "integer",
            "description": "Contacts whose language does not match `recipient_language`.",
            "examples": [
              0
            ]
          }
        }
      },
      "SendSuccessResponse": {
        "type": "object",
        "description": "The message was scheduled. `result_data` is not the message object. `result_text` is the same for an immediate send.",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "result_text": {
            "type": "string",
            "const": "Message scheduled for send"
          },
          "result_data": {
            "type": "object",
            "required": [
              "id",
              "send_time",
              "recipients"
            ],
            "properties": {
              "id": {
                "type": "string",
                "description": "The message ID, as in the URL.",
                "examples": [
                  "2001"
                ]
              },
              "send_time": {
                "type": "string",
                "description": "When the message will be sent, `Y-m-d H:i:s` in the account time zone. For an immediate send, the time of the request.",
                "examples": [
                  "2026-10-20 09:00:00"
                ]
              },
              "recipients": {
                "$ref": "#/components/schemas/Recipients"
              }
            }
          }
        }
      },
      "CreateAndSendSuccessResponse": {
        "type": "object",
        "description": "As `SendSuccessResponse`, except that `result_data.id` is an integer.",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "result_text": {
            "type": "string",
            "const": "Message scheduled for send"
          },
          "result_data": {
            "type": "object",
            "required": [
              "id",
              "send_time",
              "recipients"
            ],
            "properties": {
              "id": {
                "type": "integer",
                "description": "The new message's ID, as an integer.",
                "examples": [
                  2005
                ]
              },
              "send_time": {
                "type": "string",
                "description": "`Y-m-d H:i:s` in the account time zone; the time of the request for an immediate send.",
                "examples": [
                  "2026-10-06 14:05:31"
                ]
              },
              "recipients": {
                "$ref": "#/components/schemas/Recipients"
              }
            }
          }
        }
      },
      "SendNoRecipientsResponse": {
        "type": "object",
        "description": "`emails_to_be_sent` was `0`: the message was not scheduled and stays a `Draft`. HTTP 400, with the counts and no `errors` key or `send_time`. On `POST /messages/send`, `result_data.id` is the ID of the draft the request created.",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "result_text": {
            "type": "string",
            "const": "Email can't be sent, no contacts selected"
          },
          "result_data": {
            "type": "object",
            "required": [
              "id",
              "recipients"
            ],
            "properties": {
              "id": {
                "type": [
                  "string",
                  "integer"
                ],
                "examples": [
                  "2004"
                ]
              },
              "recipients": {
                "$ref": "#/components/schemas/Recipients"
              }
            }
          }
        }
      },
      "CreateSuccessResponse": {
        "type": "object",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "result_text": {
            "type": "string",
            "const": "Created"
          },
          "result_data": {
            "$ref": "#/components/schemas/Message"
          }
        }
      },
      "UpdateSuccessResponse": {
        "type": "object",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "result_text": {
            "type": "string",
            "const": "Updated"
          },
          "result_data": {
            "$ref": "#/components/schemas/Message"
          }
        }
      },
      "CancelSuccessResponse": {
        "type": "object",
        "description": "The message is back to `Draft` and keeps its `send_time`.",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "result_text": {
            "type": "string",
            "const": "Message reverted to draft status"
          },
          "result_data": {
            "$ref": "#/components/schemas/Message"
          }
        }
      },
      "DeleteResponse": {
        "type": "object",
        "description": "Delete result. `result_data` is always an empty array; the outcome is in `result_text`.",
        "required": [
          "success",
          "result_data",
          "result_text"
        ],
        "properties": {
          "success": {
            "type": "boolean"
          },
          "result_data": {
            "type": "array",
            "maxItems": 0
          },
          "result_text": {
            "type": "string",
            "examples": [
              "Deleted",
              "Item with id '2099' not found",
              "Message: 'Spring Season Announcement' is currently sending",
              "This message cannot be deleted because it's part of the automated series: \"Welcome Sequence\"",
              "System messages cannot be deleted",
              "Offer messages cannot be deleted"
            ]
          }
        }
      },
      "ValidationErrorResponse": {
        "type": "object",
        "description": "Validation errors. `allowed_values` is present only when a lookup field had an unknown value and the account has 50 or fewer options for it.",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "result_text": {
            "type": "string",
            "const": "Validation failed, see 'result_data' -> 'errors'"
          },
          "result_data": {
            "type": "object",
            "required": [
              "errors"
            ],
            "properties": {
              "errors": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "allowed_values": {
                "type": "object",
                "description": "Keyed by field name; each value is an object of the valid options keyed by ID. Sender and reply-to options are `Name <email>`.",
                "additionalProperties": {
                  "type": "object",
                  "additionalProperties": {
                    "type": "string"
                  }
                },
                "examples": [
                  {
                    "group": {
                      "31": "Productions",
                      "32": "Newsletters"
                    }
                  }
                ]
              }
            }
          }
        }
      },
      "PlainErrorResponse": {
        "type": "object",
        "description": "Request-shape, ID-mismatch, and server errors: the message is in `result_text` and `result_data` is an empty array.",
        "required": [
          "success",
          "result_text",
          "result_data"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "result_text": {
            "type": "string",
            "examples": [
              "Invalid JSON",
              "Invalid request",
              "Provided 'id': 2001 does not match 'id' in request body (2002)",
              "Message content error"
            ]
          },
          "result_data": {
            "type": "array",
            "maxItems": 0
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Invalid API ID or key. A `WWW-Authenticate: Basic` header is sent, and the body is the plain error envelope. The response's `Content-Type` is `text/html`, although the body is JSON.",
        "content": {
          "text/html": {
            "schema": {
              "$ref": "#/components/schemas/PlainErrorResponse"
            },
            "example": {
              "success": false,
              "result_data": [],
              "result_text": "Invalid credentials"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Valid key on a closed or inactive account.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "success": {
                  "type": "boolean",
                  "const": false
                },
                "result_data": {
                  "type": "array",
                  "maxItems": 0
                },
                "result_text": {
                  "type": "string",
                  "enum": [
                    "This account has been closed.",
                    "This account is not active."
                  ]
                }
              },
              "required": [
                "success",
                "result_data",
                "result_text"
              ]
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "The account's API limit was exceeded.",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "success": {
                  "type": "boolean",
                  "const": false
                },
                "result_text": {
                  "type": "string",
                  "description": "Names the limit that was exceeded."
                },
                "result_data": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "paths": {
    "/messages": {
      "get": {
        "summary": "List messages",
        "description": "Returns a JSON array of messages, most recently modified first, or `[]` when none match. The list contains every message in the account that has not been deleted, in every status (drafts, scheduled and sent messages, messages published to the archive, and the `Nurture` and `Transactional` messages set up in Envoke). Templates and deleted messages are not included; there is no filter by message type. `html` is never included. Supports filtering, sorting, pagination, and field selection. `limit=1` returns a bare object (or `[]`). Filter operators: equals and `(ne)` on every filterable field, plus `(or)` and `(not)` groups and numbered groups; each also has a `$` spelling (`$ne`, `$or`, `$not`). `(in)`, `(regex)`, `(gt)`, `(lt)`, `(gte)`, and `(lte)` are not supported on any message field and fail with an error naming the SQL operator (`'IN' is not a valid operator for the field 'status'`; `REGEXP`, `>`, `<`, `>=`, `<=` for the others); to match one of several values, use `(or)`. Filterable: `id`, `external_id`, `name`, `subject`, `status`, `language`, `recipient_language`, `publish_archive`, `utm_tag_links`. `create_time` and `modified_time` are sortable only. `group`, `send_time`, `sender`, `reply_to`, `recipient_subscription`, `recipient_tag`, and `recipient_segment` are not accepted as filter or sort fields; to find messages by one of these, list with `fields` and select on your side. When the request has no `filter` parameter, every query parameter other than `limit`, `skip`, `sort`, `fields`, `result_type`, and `include_html` is treated as a filter, so `?status=Sent` equals `?filter[status]=Sent`, and any other parameter is rejected (`Field 'folder' is not valid for filtering`); when `filter` is present, the other parameters are ignored as filters. Invalid parameters return 400: the `limit`, `skip`, and `sort` format errors first, then the filter and sort field errors listed together.",
        "operationId": "listMessages",
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Skip"
          },
          {
            "$ref": "#/components/parameters/Fields"
          },
          {
            "$ref": "#/components/parameters/ResultType"
          },
          {
            "name": "filter[{field}]",
            "in": "query",
            "description": "`id`, `external_id`, `name`, `subject`. Operators: equals and `(ne)`, e.g. `filter[subject]=Our spring season is here`. `name` and `subject` match the whole value, ignoring case. An unknown field gives `Field '{field}' is not valid for filtering` (an unrecognised operator such as `(foo)` gives `Field '(foo)' is not valid for filtering`); a list given to an operator (`filter[name][(ne)][]=Gala`) gives `Field 'name' requires a scalar value`.",
            "schema": {
              "type": "string"
            },
            "example": "Spring Season Announcement"
          },
          {
            "name": "filter[status]",
            "in": "query",
            "description": "A status name or code. Names are matched ignoring case and surrounding spaces, with a space and an underscore treated as the same (`Sent`, `sent`, `AB test in progress`, `ab_test_in_progress`). Codes: `Draft` 0, `Scheduled` 1, `AB test in progress` 2, `Queuing` 4, `Sending` 5, `Transactional` 7, `Nurture` 8, `Sent` 9; a code is used as given, so a number that is not a status code (e.g. `3`) is accepted and matches nothing. Operators: equals and `(ne)`, with names or codes. An unknown name gives `Invalid 'status' provided (possible values: 'Draft', 'Scheduled', 'AB test in progress', 'Queuing', 'Sending', 'Transactional', 'Nurture', 'Sent')`. `(in)` is not supported; use `(or)`.",
            "schema": {
              "type": "string"
            },
            "example": "Sent"
          },
          {
            "name": "filter[language]",
            "in": "query",
            "description": "Also `filter[recipient_language]`. Takes the language ID, not the code: `1` for English, `3` for French. `filter[language]=en` does not match English messages. Operators: equals and `(ne)`.",
            "schema": {
              "type": "integer",
              "enum": [
                1,
                3
              ]
            },
            "example": 3
          },
          {
            "name": "filter[publish_archive]",
            "in": "query",
            "description": "Also `filter[utm_tag_links]`. Takes the stored value. `publish_archive`: `2` for messages published to the archive, `1` for the rest. `utm_tag_links`: `1` for UTM tagging on, `0` for off. `true` and `false` do not work in these filters. Operators: equals and `(ne)`.",
            "schema": {
              "type": "integer",
              "enum": [
                0,
                1,
                2
              ]
            },
            "example": 2
          },
          {
            "name": "filter[(or)]",
            "in": "query",
            "description": "Matches if any of the numbered groups inside match: `filter[(or)][0][status]=Sent&filter[(or)][1][status]=Scheduled`. `$or` is the same.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filter[(not)]",
            "in": "query",
            "description": "Matches if the conditions inside do not all match: `filter[(not)][status]=Sent&filter[(not)][language]=3` excludes messages that are both sent and French. `$not` is the same.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "filter[{n}]",
            "in": "query",
            "description": "Numbered AND group, used to repeat a field: `filter[0][status][(ne)]=Draft&filter[1][status][(ne)]=Sent`. Separate filters are ANDed; groups inside `(or)` are numbered the same way.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sort[{field}]",
            "in": "query",
            "description": "Sort direction `asc` or `desc` (case-insensitive), or `1` or `-1`. Repeat for a secondary sort. A `sort` replaces the default order (`modified_time` descending). Sortable fields: `id`, `external_id`, `name`, `subject`, `status` (by status code), `language` and `recipient_language` (by language ID), `publish_archive`, `utm_tag_links`, `create_time`, `modified_time`. A field that does not exist gives `Field '{field}' is not valid for sorting`; a bad direction gives `'{value}' is not a valid sort direction`; `sort` without a field (`?sort=name`) gives `Invalid 'sort': 'name'`.",
            "schema": {
              "type": "string",
              "enum": [
                "asc",
                "desc",
                "ASC",
                "DESC",
                "1",
                "-1"
              ]
            },
            "example": "asc"
          }
        ],
        "responses": {
          "200": {
            "description": "Matching messages: an array, a key-value object with `result_type=kvp`, or a single object with `limit=1`.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Message"
                      }
                    },
                    {
                      "$ref": "#/components/schemas/Message"
                    },
                    {
                      "type": "object",
                      "description": "Message ID to name (`result_type=kvp`).",
                      "additionalProperties": {
                        "type": "string"
                      }
                    }
                  ]
                },
                "examples": {
                  "array": {
                    "summary": "Default array response",
                    "value": [
                      {
                        "id": "2001",
                        "external_id": "3e8b1f6c9a2d4e7b0c5f8a1d3e6b9c2f",
                        "preview_url": "https://engage.envoke.com/m/a7c3e9f1b5d2/m/3e8b1f6c9a2d4e7b0c5f8a1d3e6b9c2f",
                        "create_time": "2026-10-06 10:15:42",
                        "modified_time": "2026-10-06 11:02:08",
                        "name": "Spring Season Announcement",
                        "group": "Productions",
                        "status": "Scheduled",
                        "send_time": "2026-10-20 09:00:00",
                        "sender": "Box Office <boxoffice@example.org>",
                        "reply_to": "Front of House <frontofhouse@example.org>",
                        "subject": "Our spring season is here",
                        "language": "en",
                        "recipient_subscription": "Season News",
                        "recipient_tag": "",
                        "recipient_segment": "",
                        "recipient_language": "",
                        "publish_archive": true,
                        "utm_tag_links": true
                      }
                    ]
                  },
                  "fields": {
                    "summary": "Selected fields (fields=id,name,status,send_time)",
                    "value": [
                      {
                        "id": "2002",
                        "name": "Auditions Open",
                        "status": "Draft",
                        "send_time": ""
                      },
                      {
                        "id": "2001",
                        "name": "Spring Season Announcement",
                        "status": "Scheduled",
                        "send_time": "2026-10-20 09:00:00"
                      }
                    ]
                  },
                  "kvp": {
                    "summary": "Key-value response (result_type=kvp)",
                    "value": {
                      "2001": "Spring Season Announcement",
                      "2002": "Auditions Open"
                    }
                  },
                  "sent_or_scheduled": {
                    "summary": "?filter[(or)][0][status]=Sent&filter[(or)][1][status]=Scheduled&fields=id,name,status",
                    "value": [
                      {
                        "id": "2005",
                        "name": "Pirates of Penzance - Cast Notice",
                        "status": "Sent"
                      },
                      {
                        "id": "2001",
                        "name": "Spring Season Announcement",
                        "status": "Scheduled"
                      }
                    ]
                  },
                  "single": {
                    "summary": "limit=1 returns an object (?name=Auditions%20Open&fields=id,status&limit=1)",
                    "value": {
                      "id": "2002",
                      "status": "Draft"
                    }
                  },
                  "empty": {
                    "summary": "No messages match",
                    "value": []
                  }
                }
              }
            }
          },
          "400": {
            "description": "Invalid list parameters.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ValidationErrorResponse"
                },
                "examples": {
                  "filter_errors": {
                    "summary": "Unknown field and unsupported operator",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Field 'folder' is not valid for filtering",
                          "'IN' is not a valid operator for the field 'status'"
                        ]
                      }
                    }
                  },
                  "invalid_status": {
                    "summary": "Not a status name",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Invalid 'status' provided (possible values: 'Draft', 'Scheduled', 'AB test in progress', 'Queuing', 'Sending', 'Transactional', 'Nurture', 'Sent')"
                        ]
                      }
                    }
                  },
                  "paging_errors": {
                    "summary": "Bad limit and skip",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Invalid 'limit': 'ten'",
                          "Invalid 'skip': 'abc'"
                        ]
                      }
                    }
                  },
                  "sort_errors": {
                    "summary": "Unknown sort field and bad direction",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Field 'folder' is not valid for sorting",
                          "'up' is not a valid sort direction"
                        ]
                      }
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "post": {
        "summary": "Create a message",
        "description": "Creates a draft message and returns it in full, without `html`. Only `html` or `template_id` is required. Setting `send_time` stores it but does not schedule the message; to create and send in one request, use `POST /messages/send`. Read-only fields are silently ignored. A body that is not valid JSON returns the plain envelope `Invalid JSON`; no body, an empty body, or `{}` returns `Request body may not be empty`. If the content or the message cannot be saved, the plain envelope carries `Message content error` or `insert error: ` followed by the database error.",
        "operationId": "createMessage",
        "tags": [
          "Messages"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageCreate"
              },
              "examples": {
                "minimal": {
                  "summary": "HTML only (a Draft named API Message {time}, default sender, no subject or recipients)",
                  "value": {
                    "html": "<html><body><h1>Auditions Open</h1><p>Auditions for our spring production start Monday.</p></body></html>"
                  }
                },
                "full": {
                  "summary": "Every setting",
                  "value": {
                    "name": "Spring Season Announcement",
                    "group": "Productions",
                    "subject": "Our spring season is here",
                    "sender": "boxoffice@example.org",
                    "reply_to": 413,
                    "language": "en",
                    "recipient_subscription": "Season News",
                    "publish_archive": true,
                    "utm_tag_links": true,
                    "html": "<html><body><h1>Spring Season</h1><p>Four shows, one season pass.</p></body></html>"
                  }
                },
                "template_replace": {
                  "summary": "From a template, with its placeholders filled in",
                  "value": {
                    "name": "Pirates of Penzance - Opening Night",
                    "template_id": 1901,
                    "subject": "Opening night is almost here",
                    "replace": {
                      "{SHOW_TITLE}": "The Pirates of Penzance",
                      "{CURTAIN_TIME}": "7:30 p.m."
                    }
                  }
                },
                "master_template": {
                  "summary": "A block of HTML inserted into a master template",
                  "value": {
                    "name": "Weekly Show Listings",
                    "template_id": 1902,
                    "replace": {
                      "{SHOW_LISTINGS}": "<h2>This week</h2><p>Fri 7:30 p.m. - The Pirates of Penzance</p><p>Sat 2:00 p.m. - Matinee</p>"
                    }
                  }
                },
                "own_html_replace": {
                  "summary": "Your own HTML with placeholders",
                  "value": {
                    "name": "Opening Date Notice",
                    "html": "<html><body><p>Dear patron, {SHOW_TITLE} opens on {OPENING_DATE}.</p></body></html>",
                    "replace": {
                      "{SHOW_TITLE}": "The Pirates of Penzance",
                      "{OPENING_DATE}": "Friday, October 23"
                    }
                  }
                },
                "narrowed": {
                  "summary": "To a subscription, narrowed by a tag",
                  "value": {
                    "name": "Cast Party Invitation",
                    "subject": "You are invited to the cast party",
                    "recipient_subscription": "Season News",
                    "recipient_tag": "Cast Call",
                    "html": "<html><body><p>Join us after the final show.</p></body></html>"
                  }
                },
                "send_time": {
                  "summary": "A draft with a stored send time (stays a Draft)",
                  "value": {
                    "name": "Subscriber Presale",
                    "subject": "Presale opens Tuesday at 9",
                    "recipient_subscription": "Season News",
                    "send_time": "2026-10-20 09:00:00",
                    "html": "<html><body><p>Subscribers get first pick of seats.</p></body></html>"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Message created. `result_data` is the full message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateSuccessResponse"
                },
                "example": {
                  "success": true,
                  "result_text": "Created",
                  "result_data": {
                    "id": "2001",
                    "external_id": "3e8b1f6c9a2d4e7b0c5f8a1d3e6b9c2f",
                    "preview_url": "https://engage.envoke.com/m/a7c3e9f1b5d2/m/3e8b1f6c9a2d4e7b0c5f8a1d3e6b9c2f",
                    "create_time": "2026-10-06 10:15:42",
                    "modified_time": "2026-10-06 10:15:42",
                    "name": "Spring Season Announcement",
                    "group": "Productions",
                    "status": "Draft",
                    "send_time": "",
                    "sender": "Box Office <boxoffice@example.org>",
                    "reply_to": "Front of House <frontofhouse@example.org>",
                    "subject": "Our spring season is here",
                    "language": "en",
                    "recipient_subscription": "Season News",
                    "recipient_tag": "",
                    "recipient_segment": "",
                    "recipient_language": "",
                    "publish_archive": true,
                    "utm_tag_links": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed, the body is not valid JSON, or the message could not be saved.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ValidationErrorResponse"
                    },
                    {
                      "$ref": "#/components/schemas/PlainErrorResponse"
                    }
                  ]
                },
                "examples": {
                  "empty_body": {
                    "summary": "No body, an empty body, or {}",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Request body may not be empty"
                        ]
                      }
                    }
                  },
                  "missing_content": {
                    "summary": "Neither html nor template_id",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "One of the fields 'template_id' or 'html' must be provided in order to create a message"
                        ]
                      }
                    }
                  },
                  "tag_and_segment": {
                    "summary": "Tag and segment together",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Only one of the fields 'recipient_tag' or 'recipient_segment' may be provided"
                        ]
                      }
                    }
                  },
                  "template_not_found": {
                    "summary": "template_id matches nothing",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Nothing found for 'template_id': 1999"
                        ]
                      }
                    }
                  },
                  "field_errors": {
                    "summary": "Field checks, collected",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "'headline' is not a valid field",
                          "The field 'name' must be at least 3 characters long",
                          "The field 'subject' must be at least 6 characters long",
                          "The field 'send_time' must be in the future"
                        ]
                      }
                    }
                  },
                  "unknown_group": {
                    "summary": "Unknown group, with allowed_values",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Invalid 'group': 'Playbills'"
                        ],
                        "allowed_values": {
                          "group": {
                            "31": "Productions",
                            "32": "Newsletters"
                          }
                        }
                      }
                    }
                  },
                  "unknown_sender": {
                    "summary": "No identity has that address",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "No sender identity found for email: 'tickets@example.org'"
                        ],
                        "allowed_values": {
                          "sender": {
                            "412": "Box Office <boxoffice@example.org>",
                            "413": "Front of House <frontofhouse@example.org>"
                          }
                        }
                      }
                    }
                  },
                  "invalid_language": {
                    "summary": "Unknown language",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Invalid 'language': 'de'"
                        ],
                        "allowed_values": {
                          "language": {
                            "1": "en",
                            "3": "fr"
                          }
                        }
                      }
                    }
                  },
                  "invalid_replace": {
                    "summary": "replace is not an object of strings with {...} keys",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Invalid 'replace' data"
                        ]
                      }
                    }
                  },
                  "html_too_large": {
                    "summary": "html over 204,800 bytes (checked before replace)",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "The field 'html' may not be longer than 200kB"
                        ]
                      }
                    }
                  },
                  "invalid_sender": {
                    "summary": "Not an identity ID, Name <email>, or email address",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Invalid 'sender': 'Box Office'"
                        ],
                        "allowed_values": {
                          "sender": {
                            "412": "Box Office <boxoffice@example.org>",
                            "413": "Front of House <frontofhouse@example.org>"
                          }
                        }
                      }
                    }
                  },
                  "unknown_tag": {
                    "summary": "Unknown recipient tag",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Invalid 'recipient_tag': 'Cast List'"
                        ],
                        "allowed_values": {
                          "recipient_tag": {
                            "57": "Cast Call",
                            "58": "Crew"
                          }
                        }
                      }
                    }
                  },
                  "invalid_send_time": {
                    "summary": "send_time is not a date",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Invalid 'send_time': 'next Friday evening'"
                        ]
                      }
                    }
                  },
                  "insert_error": {
                    "summary": "The message could not be saved (plain envelope; the database error follows the prefix)",
                    "value": {
                      "success": false,
                      "result_data": [],
                      "result_text": "insert error: {database error}"
                    }
                  },
                  "content_error": {
                    "summary": "The content could not be saved (plain envelope)",
                    "value": {
                      "success": false,
                      "result_data": [],
                      "result_text": "Message content error"
                    }
                  },
                  "invalid_json": {
                    "summary": "Body is not valid JSON",
                    "value": {
                      "success": false,
                      "result_data": [],
                      "result_text": "Invalid JSON"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/messages/send": {
      "post": {
        "summary": "Create and send a message",
        "description": "Creates a draft from a full create body and sends it, in one request. Without `send_time` the message is sent immediately (on the next run of Envoke's sending process); with it, at that time. The body must include content, a subject, and recipients; `sender` falls back to the account default. On success, `result_data.id` is an integer. If the create step fails, nothing is created and the create error is returned. If the create step succeeds but the send step fails (for example, no subject, or out of credits), the draft is kept and its ID is not returned; the response is the send error only. The zero-recipient response is the one exception: its `result_data.id` is the new draft's ID. Give each message a unique `name` so that a leftover draft can be found with `GET /messages?name=...`.",
        "operationId": "createAndSendMessage",
        "tags": [
          "Sending"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageCreate"
              },
              "examples": {
                "template_now": {
                  "summary": "From a template, sent immediately",
                  "value": {
                    "name": "Pirates of Penzance - Cast Notice",
                    "template_id": 1901,
                    "replace": {
                      "{SHOW_TITLE}": "The Pirates of Penzance",
                      "{CURTAIN_TIME}": "7:30 p.m."
                    },
                    "subject": "Call times for opening night",
                    "sender": "Box Office <boxoffice@example.org>",
                    "recipient_tag": "Cast Call"
                  }
                },
                "scheduled": {
                  "summary": "Scheduled for an account-local time",
                  "value": {
                    "name": "Subscriber Presale",
                    "subject": "Presale opens Tuesday at 9",
                    "recipient_subscription": "Season News",
                    "send_time": "2026-10-20 09:00:00",
                    "html": "<html><body><p>Subscribers get first pick of seats.</p></body></html>"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Message created and scheduled. `result_data.id` is an integer.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateAndSendSuccessResponse"
                },
                "examples": {
                  "immediate": {
                    "summary": "Sent now (id is an integer)",
                    "value": {
                      "success": true,
                      "result_text": "Message scheduled for send",
                      "result_data": {
                        "id": 2005,
                        "send_time": "2026-10-06 14:05:31",
                        "recipients": {
                          "total_selected": 2,
                          "emails_to_be_sent": 2,
                          "excluded_unsubscribed_contacts": 0,
                          "excluded_invalid_emails": 0,
                          "excluded_bounced_emails": 0,
                          "excluded_unverified_emails": 0,
                          "excluded_by_language": 0
                        }
                      }
                    }
                  },
                  "scheduled": {
                    "summary": "Scheduled with send_time in the body",
                    "value": {
                      "success": true,
                      "result_text": "Message scheduled for send",
                      "result_data": {
                        "id": 2007,
                        "send_time": "2026-10-20 09:00:00",
                        "recipients": {
                          "total_selected": 412,
                          "emails_to_be_sent": 377,
                          "excluded_unsubscribed_contacts": 29,
                          "excluded_invalid_emails": 2,
                          "excluded_bounced_emails": 4,
                          "excluded_unverified_emails": 0,
                          "excluded_by_language": 0
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "A create error (nothing created), a send error (the draft is kept), zero recipients (the draft is kept and its ID returned), or a body that is not valid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ValidationErrorResponse"
                    },
                    {
                      "$ref": "#/components/schemas/SendNoRecipientsResponse"
                    },
                    {
                      "$ref": "#/components/schemas/PlainErrorResponse"
                    }
                  ]
                },
                "examples": {
                  "empty_body": {
                    "summary": "No body, an empty body, or {}",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Request body may not be empty"
                        ]
                      }
                    }
                  },
                  "create_error": {
                    "summary": "Create step failed; nothing created",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "One of the fields 'template_id' or 'html' must be provided in order to create a message"
                        ]
                      }
                    }
                  },
                  "send_error": {
                    "summary": "Send step failed; the draft is kept and its ID is not returned",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "The field 'subject' must be set"
                        ]
                      }
                    }
                  },
                  "out_of_credits": {
                    "summary": "Prepaid credit balance below emails_to_be_sent",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Email can't be sent, the account is out of credits"
                        ]
                      }
                    }
                  },
                  "no_recipients": {
                    "summary": "No contacts selected; result_data.id is the new draft",
                    "value": {
                      "success": false,
                      "result_text": "Email can't be sent, no contacts selected",
                      "result_data": {
                        "id": 2006,
                        "recipients": {
                          "total_selected": 0,
                          "emails_to_be_sent": 0,
                          "excluded_unsubscribed_contacts": 0,
                          "excluded_invalid_emails": 0,
                          "excluded_bounced_emails": 0,
                          "excluded_unverified_emails": 0,
                          "excluded_by_language": 0
                        }
                      }
                    }
                  },
                  "invalid_json": {
                    "summary": "Body is not valid JSON",
                    "value": {
                      "success": false,
                      "result_data": [],
                      "result_text": "Invalid JSON"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/messages/{id}": {
      "get": {
        "summary": "Get a message",
        "description": "Returns a single message object, the same shape as one item of the list. `fields` selects fields; filter, sort, paging, and `result_type` parameters are ignored. Add `include_html=1` to append the HTML as the last key. If the ID is not found, or belongs to a deleted message or a template, the response is an empty array (`[]`) with HTTP 200.",
        "operationId": "getMessage",
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MessageId"
          },
          {
            "$ref": "#/components/parameters/IncludeHtml"
          },
          {
            "$ref": "#/components/parameters/Fields"
          }
        ],
        "responses": {
          "200": {
            "description": "The message, or `[]` when not found.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Message"
                    },
                    {
                      "type": "array",
                      "maxItems": 0
                    }
                  ]
                },
                "examples": {
                  "with_html": {
                    "summary": "?include_html=1",
                    "value": {
                      "id": "2003",
                      "external_id": "9c4d2a7e1f5b3d8a6c0e4f2b7d9a1c5e",
                      "preview_url": "https://engage.envoke.com/m/a7c3e9f1b5d2/m/9c4d2a7e1f5b3d8a6c0e4f2b7d9a1c5e",
                      "create_time": "2026-10-06 10:40:11",
                      "modified_time": "2026-10-06 10:40:11",
                      "name": "Pirates of Penzance - Opening Night",
                      "group": "",
                      "status": "Draft",
                      "send_time": "",
                      "sender": "Box Office <boxoffice@example.org>",
                      "reply_to": null,
                      "subject": "",
                      "language": "en",
                      "recipient_subscription": "",
                      "recipient_tag": "",
                      "recipient_segment": "",
                      "recipient_language": "",
                      "publish_archive": false,
                      "utm_tag_links": false,
                      "html": "<html><body><h1>The Pirates of Penzance</h1><p>Curtain at 7:30 p.m.</p></body></html>"
                    }
                  },
                  "fields": {
                    "summary": "?fields=id,name,status",
                    "value": {
                      "id": "2001",
                      "name": "Spring Season Announcement",
                      "status": "Scheduled"
                    }
                  },
                  "not_found": {
                    "summary": "Not found, deleted, or a template",
                    "value": []
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "patch": {
        "summary": "Update a draft message",
        "description": "Changes a `Draft` message and returns it in full, without `html`. Send only the fields to change; the recipient fields are replaced as a set: when a request includes any one of `recipient_subscription`, `recipient_tag`, or `recipient_segment`, the other two are cleared unless they are also in the request (`recipient_language` is never cleared by this rule). `html` replaces the whole content; `replace` works only with `html` in the same request. Every successful update sets `modified_time`, even when no value changed. Read-only fields are silently ignored. A missing message (including a deleted message or a template) gives `No message found for 'id': {id}`. Body `id` different from the URL gives the plain envelope `Provided 'id': {id} does not match 'id' in request body ({body id})`. No body, an empty body, or `{}` gives `Request body may not be empty`.",
        "operationId": "updateMessage",
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MessageId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageUpdate"
              },
              "examples": {
                "subject_html": {
                  "summary": "Change the subject and HTML",
                  "value": {
                    "subject": "Auditions start Monday at 7",
                    "html": "<html><body><h1>Auditions Open</h1><p>Monday at 7 p.m. in the rehearsal hall.</p></body></html>"
                  }
                },
                "recipients": {
                  "summary": "Send to a tag instead of a subscription (the subscription is cleared)",
                  "value": {
                    "recipient_tag": "Cast Call"
                  }
                },
                "clear_reply_to": {
                  "summary": "Clear the reply-to (read back as null)",
                  "value": {
                    "reply_to": ""
                  }
                },
                "send_time": {
                  "summary": "Store a send time (stays a Draft)",
                  "value": {
                    "send_time": "2026-10-20 09:00:00"
                  }
                },
                "html_replace": {
                  "summary": "New HTML with placeholders",
                  "value": {
                    "html": "<html><body><h1>{SHOW_TITLE}</h1><p>Curtain at {CURTAIN_TIME}.</p></body></html>",
                    "replace": {
                      "{SHOW_TITLE}": "The Pirates of Penzance",
                      "{CURTAIN_TIME}": "8:00 p.m."
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Message updated. `result_data` is the full message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateSuccessResponse"
                },
                "example": {
                  "success": true,
                  "result_text": "Updated",
                  "result_data": {
                    "id": "2002",
                    "external_id": "5a9e3c1f7b2d4a6e8c0f1b3d5e7a9c2b",
                    "preview_url": "https://engage.envoke.com/m/a7c3e9f1b5d2/m/5a9e3c1f7b2d4a6e8c0f1b3d5e7a9c2b",
                    "create_time": "2026-10-06 10:22:09",
                    "modified_time": "2026-10-06 11:30:17",
                    "name": "Auditions Open",
                    "group": "",
                    "status": "Draft",
                    "send_time": "",
                    "sender": "Box Office <boxoffice@example.org>",
                    "reply_to": null,
                    "subject": "Auditions start Monday at 7",
                    "language": "en",
                    "recipient_subscription": "",
                    "recipient_tag": "Cast Call",
                    "recipient_segment": "",
                    "recipient_language": "",
                    "publish_archive": false,
                    "utm_tag_links": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed, the ID in the body does not match, or the body is not valid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ValidationErrorResponse"
                    },
                    {
                      "$ref": "#/components/schemas/PlainErrorResponse"
                    }
                  ]
                },
                "examples": {
                  "empty_body": {
                    "summary": "No body, an empty body, or {}",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Request body may not be empty"
                        ]
                      }
                    }
                  },
                  "html_too_large": {
                    "summary": "html over 204,800 bytes",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "The field 'html' may not be longer than 200kB"
                        ]
                      }
                    }
                  },
                  "tag_and_segment": {
                    "summary": "Tag and segment together",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Only one of the fields 'recipient_tag' or 'recipient_segment' may be provided"
                        ]
                      }
                    }
                  },
                  "field_errors": {
                    "summary": "Field checks, collected (returned before any replace error)",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "'headline' is not a valid field",
                          "The field 'name' must be at least 3 characters long",
                          "Invalid 'recipient_tag': 'Cast List'"
                        ]
                      }
                    }
                  },
                  "invalid_replace": {
                    "summary": "replace is not an object of strings with {...} keys",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Invalid 'replace' data"
                        ]
                      }
                    }
                  },
                  "past_send_time": {
                    "summary": "send_time in the past, or \"\" / null (read as now)",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "The field 'send_time' must be in the future"
                        ]
                      }
                    }
                  },
                  "invalid_json": {
                    "summary": "Body is not valid JSON",
                    "value": {
                      "success": false,
                      "result_data": [],
                      "result_text": "Invalid JSON"
                    }
                  },
                  "not_found": {
                    "summary": "No message with this ID",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "No message found for 'id': 2099"
                        ]
                      }
                    }
                  },
                  "not_draft": {
                    "summary": "The message is not a Draft",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Only 'Draft' messages can be updated, this message has the status: 'Scheduled'"
                        ]
                      }
                    }
                  },
                  "template_id": {
                    "summary": "template_id is create-only",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "The field 'template_id' is not valid for updating a message"
                        ]
                      }
                    }
                  },
                  "replace_without_html": {
                    "summary": "replace sent without html",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "The field 'html' is required for content 'replace' data"
                        ]
                      }
                    }
                  },
                  "number_zero": {
                    "summary": "A JSON number 0 does not clear a lookup field",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Invalid 'group': '0'"
                        ],
                        "allowed_values": {
                          "group": {
                            "31": "Productions",
                            "32": "Newsletters"
                          }
                        }
                      }
                    }
                  },
                  "id_mismatch": {
                    "summary": "Body id differs from the URL (plain envelope)",
                    "value": {
                      "success": false,
                      "result_data": [],
                      "result_text": "Provided 'id': 2001 does not match 'id' in request body (2002)"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      },
      "delete": {
        "summary": "Delete a message",
        "description": "Moves the message to the Deleted folder in Envoke, where it can be restored only in the Envoke app until Envoke's scheduled cleanup removes it permanently. A GET for it then returns `[]` and it is no longer listed. Deleting a `Scheduled` message also sets it back to `Draft`, so it is not sent; there is no need to cancel it first. Messages in most statuses can be deleted, including `Sent` messages; a message that is `Sending`, a `Nurture` message in a nurture series, and Envoke system or offer messages cannot. Every response uses the plain envelope with `result_data: []`. If the delete fails on the server, `result_text` is `Delete error: ` followed by the error detail.",
        "operationId": "deleteMessage",
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MessageId"
          }
        ],
        "responses": {
          "200": {
            "description": "Message deleted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteResponse"
                },
                "example": {
                  "success": true,
                  "result_data": [],
                  "result_text": "Deleted"
                }
              }
            }
          },
          "400": {
            "description": "Not found, blocked, or a server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteResponse"
                },
                "examples": {
                  "not_found": {
                    "summary": "No message has this ID",
                    "value": {
                      "success": false,
                      "result_data": [],
                      "result_text": "Item with id '2099' not found"
                    }
                  },
                  "sending": {
                    "summary": "The message is Sending",
                    "value": {
                      "success": false,
                      "result_data": [],
                      "result_text": "Message: 'Spring Season Announcement' is currently sending"
                    }
                  },
                  "nurture_series": {
                    "summary": "Part of a nurture series",
                    "value": {
                      "success": false,
                      "result_data": [],
                      "result_text": "This message cannot be deleted because it's part of the automated series: \"Welcome Sequence\""
                    }
                  },
                  "system": {
                    "summary": "A message Envoke manages",
                    "value": {
                      "success": false,
                      "result_data": [],
                      "result_text": "System messages cannot be deleted"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/messages/{id}/send": {
      "post": {
        "summary": "Send a draft message",
        "description": "Sends a `Draft` message now or at its send time. The body is optional and can contain any update field (most often `send_time`); the fields are saved first, exactly as a `PATCH` would, and stay saved even if the send then fails. After any update, the message must have at least one recipient field, a subject, and a sender; the three errors are returned together. `name` is not required. Send time: with `send_time` in the body, at that time (it must be in the future and is read as account-local time); with no `send_time` in the body and none stored, immediately (on the next run of Envoke's sending process); with no `send_time` in the body and one stored, at the stored time, which is not checked again, so a stored time that has passed (for example, on a cancelled message) sends the message on the next run. Send a new `send_time` when re-sending a cancelled message. The response's counts are an estimate; `result_data.id` is a string. When `emails_to_be_sent` is `0`, the message is not scheduled and the response is HTTP 400 with the counts. A prepaid account whose credit balance is below `emails_to_be_sent` gets `Email can't be sent, the account is out of credits`; a monthly-billed account not yet on a paid plan, with too few credits, gets `Email can't be sent, the account has not upgraded`; the message stays a `Draft`.",
        "operationId": "sendMessage",
        "tags": [
          "Sending"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MessageId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendRequest"
              },
              "examples": {
                "scheduled": {
                  "summary": "Schedule for 9:00 a.m. account time (no offset or Z)",
                  "value": {
                    "send_time": "2026-10-20 09:00:00"
                  }
                },
                "recipients": {
                  "summary": "Change the recipients and send now",
                  "value": {
                    "recipient_tag": "Understudies"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Message scheduled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendSuccessResponse"
                },
                "examples": {
                  "scheduled": {
                    "summary": "Scheduled for a future send_time",
                    "value": {
                      "success": true,
                      "result_text": "Message scheduled for send",
                      "result_data": {
                        "id": "2001",
                        "send_time": "2026-10-20 09:00:00",
                        "recipients": {
                          "total_selected": 412,
                          "emails_to_be_sent": 377,
                          "excluded_unsubscribed_contacts": 29,
                          "excluded_invalid_emails": 2,
                          "excluded_bounced_emails": 4,
                          "excluded_unverified_emails": 0,
                          "excluded_by_language": 0
                        }
                      }
                    }
                  },
                  "immediate": {
                    "summary": "Sent now: send_time is the time of the request",
                    "value": {
                      "success": true,
                      "result_text": "Message scheduled for send",
                      "result_data": {
                        "id": "2002",
                        "send_time": "2026-10-06 11:41:27",
                        "recipients": {
                          "total_selected": 38,
                          "emails_to_be_sent": 35,
                          "excluded_unsubscribed_contacts": 2,
                          "excluded_invalid_emails": 0,
                          "excluded_bounced_emails": 1,
                          "excluded_unverified_emails": 0,
                          "excluded_by_language": 0
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation failed, zero recipients, the ID in the body does not match, or the body is not valid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ValidationErrorResponse"
                    },
                    {
                      "$ref": "#/components/schemas/SendNoRecipientsResponse"
                    },
                    {
                      "$ref": "#/components/schemas/PlainErrorResponse"
                    }
                  ]
                },
                "examples": {
                  "missing_settings": {
                    "summary": "No recipients, subject, or sender",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "At least one of the fields 'recipient_subscription', 'recipient_tag', or 'recipient_segment' must be set",
                          "The field 'subject' must be set",
                          "The field 'sender' must be set"
                        ]
                      }
                    }
                  },
                  "not_draft": {
                    "summary": "The message is not a Draft",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Only 'Draft' messages can be updated, this message has the status: 'Scheduled'"
                        ]
                      }
                    }
                  },
                  "not_found": {
                    "summary": "No message with this ID",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "No message found for 'id': 2099"
                        ]
                      }
                    }
                  },
                  "past_send_time": {
                    "summary": "send_time in the past",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "The field 'send_time' must be in the future"
                        ]
                      }
                    }
                  },
                  "out_of_credits": {
                    "summary": "Prepaid credit balance below emails_to_be_sent",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Email can't be sent, the account is out of credits"
                        ]
                      }
                    }
                  },
                  "not_upgraded": {
                    "summary": "Monthly-billed account not yet on a paid plan",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Email can't be sent, the account has not upgraded"
                        ]
                      }
                    }
                  },
                  "no_recipients": {
                    "summary": "No contacts selected; the message stays a Draft",
                    "value": {
                      "success": false,
                      "result_text": "Email can't be sent, no contacts selected",
                      "result_data": {
                        "id": "2004",
                        "recipients": {
                          "total_selected": 0,
                          "emails_to_be_sent": 0,
                          "excluded_unsubscribed_contacts": 0,
                          "excluded_invalid_emails": 0,
                          "excluded_bounced_emails": 0,
                          "excluded_unverified_emails": 0,
                          "excluded_by_language": 0
                        }
                      }
                    }
                  },
                  "id_mismatch": {
                    "summary": "Body id differs from the URL (plain envelope)",
                    "value": {
                      "success": false,
                      "result_data": [],
                      "result_text": "Provided 'id': 2001 does not match 'id' in request body (2002)"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    },
    "/messages/{id}/cancel": {
      "post": {
        "summary": "Cancel a scheduled send",
        "description": "Returns a `Scheduled` message to `Draft` so that it will not be sent, and returns the message in full. The message keeps its `send_time`; sending it again with no `send_time` reuses the stored time, which may now be in the past. The body is optional; update fields in it are applied after the message is returned to `Draft`, and if one fails validation the message is already a `Draft` but the response is the validation error. The API also accepts cancel for `Queuing`, `Sending`, and `AB test in progress` messages and sets them to `Draft`, but emails already queued are still delivered: only cancel scheduled messages. Any other status gives `Sending cannot be cancelled for a message with 'status': {Status}`, with the status name not quoted; a missing ID gives the same error with the status left blank.",
        "operationId": "cancelMessage",
        "tags": [
          "Sending"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/MessageId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendRequest"
              },
              "examples": {
                "empty": {
                  "summary": "No body",
                  "value": {}
                },
                "with_update": {
                  "summary": "Cancel and change the subject",
                  "value": {
                    "subject": "Presale moves to Wednesday"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Message returned to `Draft`. `result_data` is the full message.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CancelSuccessResponse"
                },
                "example": {
                  "success": true,
                  "result_text": "Message reverted to draft status",
                  "result_data": {
                    "id": "2001",
                    "external_id": "3e8b1f6c9a2d4e7b0c5f8a1d3e6b9c2f",
                    "preview_url": "https://engage.envoke.com/m/a7c3e9f1b5d2/m/3e8b1f6c9a2d4e7b0c5f8a1d3e6b9c2f",
                    "create_time": "2026-10-06 10:15:42",
                    "modified_time": "2026-10-06 11:45:03",
                    "name": "Spring Season Announcement",
                    "group": "Productions",
                    "status": "Draft",
                    "send_time": "2026-10-20 09:00:00",
                    "sender": "Box Office <boxoffice@example.org>",
                    "reply_to": "Front of House <frontofhouse@example.org>",
                    "subject": "Our spring season is here",
                    "language": "en",
                    "recipient_subscription": "Season News",
                    "recipient_tag": "",
                    "recipient_segment": "",
                    "recipient_language": "",
                    "publish_archive": true,
                    "utm_tag_links": true
                  }
                }
              }
            }
          },
          "400": {
            "description": "The message cannot be cancelled, a body field failed validation, the ID in the body does not match, or the body is not valid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ValidationErrorResponse"
                    },
                    {
                      "$ref": "#/components/schemas/PlainErrorResponse"
                    }
                  ]
                },
                "examples": {
                  "wrong_status": {
                    "summary": "Not scheduled or sending",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Sending cannot be cancelled for a message with 'status': Draft"
                        ]
                      }
                    }
                  },
                  "not_found": {
                    "summary": "No message with this ID (status left blank)",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Sending cannot be cancelled for a message with 'status': "
                        ]
                      }
                    }
                  },
                  "body_field_error": {
                    "summary": "A body field failed; the message is already a Draft",
                    "value": {
                      "success": false,
                      "result_text": "Validation failed, see 'result_data' -> 'errors'",
                      "result_data": {
                        "errors": [
                          "Invalid 'group': 'Playbills'"
                        ],
                        "allowed_values": {
                          "group": {
                            "31": "Productions",
                            "32": "Newsletters"
                          }
                        }
                      }
                    }
                  },
                  "id_mismatch": {
                    "summary": "Body id differs from the URL (plain envelope)",
                    "value": {
                      "success": false,
                      "result_data": [],
                      "result_text": "Provided 'id': 2001 does not match 'id' in request body (2002)"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        }
      }
    }
  }
}
