{
  "openapi": "3.1.1",
  "info": {
    "title": "MailKite API",
    "version": "0.19.0",
    "description": "Canonical interface contract for every MailKite SDK. One low-level request() plus one function per endpoint. All languages expose the same shape; only naming adapts to each language's convention (e.g. Go exports PascalCase).",
    "contact": {
      "name": "MailKite",
      "url": "https://mailkite.dev/contact"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://mailkite.dev/terms"
    }
  },
  "servers": [
    {
      "url": "https://api.mailkite.dev",
      "description": "Production"
    }
  ],
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "tags": [
    {
      "name": "Sending"
    },
    {
      "name": "Events"
    },
    {
      "name": "Sequences"
    },
    {
      "name": "Templates"
    },
    {
      "name": "Domains"
    },
    {
      "name": "Routes"
    },
    {
      "name": "Agents & routing"
    },
    {
      "name": "Messages"
    },
    {
      "name": "Deliveries"
    },
    {
      "name": "Realtime"
    },
    {
      "name": "Lists"
    },
    {
      "name": "Broadcasts"
    },
    {
      "name": "Docs"
    },
    {
      "name": "Schemas"
    },
    {
      "name": "Auth"
    },
    {
      "name": "Account linking"
    },
    {
      "name": "Account"
    },
    {
      "name": "Mailbox"
    },
    {
      "name": "Suppressions"
    }
  ],
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "paths": {
    "/v1/send": {
      "post": {
        "operationId": "send",
        "summary": "Send a message over a verified domain.",
        "description": "Send a message over a verified domain. Pass `templateId` (+ optional `templateData`) to send from a saved or base template.",
        "tags": [
          "Sending"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/send-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/send-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/send/batch": {
      "post": {
        "operationId": "sendBatch",
        "summary": "Send one personalized message per recipient (up to 50) in a single call.",
        "description": "Send one personalized message per recipient (up to 50) in a single call. Shared fields form the base message; each `recipients[]` entry gets its own message to exactly one address, with per-recipient `templateData` and `headers` merged over the shared ones. Every message passes the same gates as send() and gets its own id; the response reports each recipient's outcome in order, so a batch can partially succeed. Pass `scheduledAt` to park the whole batch for later (one cancelable ssnd_… per recipient).",
        "tags": [
          "Sending"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/batch-send-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/batch-send-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/events": {
      "post": {
        "operationId": "sendEvent",
        "summary": "Record one application-level fact about a user — user.created, trial.expiring, payment.failed.",
        "description": "Record one application-level fact about a user — `user.created`, `trial.expiring`, `payment.failed`. THE primary way a sequence starts on a developer platform: your application already knows when a payment failed, so it says so, and every enabled trigger listening for that name enrolls the contact with the payload as its input. Identify the subject with `email` or `contactId` (never both). Pass a `dedupeKey` to make retries idempotent: a repeat returns the original event with `duplicate: true` rather than enrolling anyone twice.",
        "tags": [
          "Events"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/send-event-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/send-event-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "get": {
        "operationId": "listEvents",
        "summary": "List recorded events, newest first — the surface for confirming a POST landed and for debugging a sequence that did not trigger.",
        "description": "List recorded events, newest first — the surface for confirming a POST landed and for debugging a sequence that did not trigger. Filter by `name` and/or `email`.",
        "tags": [
          "Events"
        ],
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": false,
            "description": "Return only events with this exact name. Omit for all names.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "query",
            "required": false,
            "description": "Return only events about this address (case-insensitive). Omit for all contacts.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows to return, 1-200. Omitted = newest 50.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/list-events-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/events/names": {
      "get": {
        "operationId": "listEventNames",
        "summary": "List the distinct event names this account works with, so an editor can offer them instead of asking you to remember one.",
        "description": "List the distinct event names this account works with, so an editor can offer them instead of asking you to remember one. Returns both events actually posted (with a count and when one last arrived) and events your sequences already trigger on or wait for but that may never have been sent — sequences are routinely written before the application emits the event, so a list of only-what-you-have-sent would be a trap.",
        "tags": [
          "Events"
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/list-event-names-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/sequences": {
      "get": {
        "operationId": "listSequences",
        "summary": "List your sequences, newest first, each with live enrollment counts.",
        "description": "List your sequences, newest first, each with live enrollment counts. Archived sequences are omitted.",
        "tags": [
          "Sequences"
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/list-sequences-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "post": {
        "operationId": "createSequence",
        "summary": "Create a sequence: a declared input shape, the steps a contact walks over time, and zero or more triggers.",
        "description": "Create a sequence: a declared input shape, the steps a contact walks over time, and zero or more triggers. A sequence is a function — `input` is its signature, and every door must satisfy it, so a step's {{input.field}} means the same thing however it was started. Created as a draft unless you pass status \"active\". The whole definition is validated up front and every problem is reported at once, so you fix a program in one pass rather than one 400 at a time.",
        "tags": [
          "Sequences"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/create-sequence-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/sequence-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/sequences/{id}": {
      "get": {
        "operationId": "getSequence",
        "summary": "Get one sequence with its definition and live enrollment counts.",
        "description": "Get one sequence with its definition and live enrollment counts.",
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/sequence-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "patch": {
        "operationId": "updateSequence",
        "summary": "Edit a sequence. Changing the STEPS bumps its version and contacts already in flight keep walking the version they started on, so an edit can never make some…",
        "description": "Edit a sequence. Changing the STEPS bumps its version and contacts already in flight keep walking the version they started on, so an edit can never make someone skip or repeat a step. Nothing else re-pins anybody — a rename, a pause, the input signature, and the trigger set are all editable while people are mid-flight. Archiving retires everyone in flight; pausing deliberately does not, so unpausing resumes rather than restarts.",
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/update-sequence-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/sequence-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "delete": {
        "operationId": "deleteSequence",
        "summary": "Delete a sequence and retire every contact still walking it.",
        "description": "Delete a sequence and retire every contact still walking it. The response reports how many were canceled.",
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/delete-sequence-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/sequences/{id}/triggers": {
      "get": {
        "operationId": "listTriggers",
        "summary": "List the triggers attached to a sequence — the doors into it.",
        "description": "List the triggers attached to a sequence — the doors into it.",
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/list-triggers-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "post": {
        "operationId": "createTrigger",
        "summary": "Attach a trigger: when this event arrives, enroll the contact it is about.",
        "description": "Attach a trigger: when this event arrives, enroll the contact it is about. Attaching never bumps the sequence's version and never touches anyone already in flight — a trigger is a fact about the outside world, a sequence is a program, and they change on different rhythms. The sequence needs a `from` address first, since an event-triggered enrollment has no message to inherit a sender from.",
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/create-trigger-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/trigger-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/triggers/{id}": {
      "patch": {
        "operationId": "updateTrigger",
        "summary": "Edit a trigger, or toggle enabled to switch the door off without deleting it.",
        "description": "Edit a trigger, or toggle `enabled` to switch the door off without deleting it. Either way, everyone already walking the sequence carries on.",
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/update-trigger-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/trigger-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "delete": {
        "operationId": "deleteTrigger",
        "summary": "Detach a trigger. Stops future enrollments through that door and nothing else.",
        "description": "Detach a trigger. Stops future enrollments through that door and nothing else.",
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/delete-trigger-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/sequences/{sequence}/start": {
      "post": {
        "operationId": "startSequence",
        "summary": "Start a sequence for one contact, directly — when your code already knows WHICH sequence it wants.",
        "description": "Start a sequence for one contact, directly — when your code already knows WHICH sequence it wants. Takes a sequence NAME or id, so \"start the dunning sequence\" needs no lookup. Returns the enrollment it created: the run you then inspect, follow, or cancel. Refused with 409 when the address is suppressed, is already running on a `reentry: \"once\"` sequence, or is missing a required input — and the response says which. Reach for sendEvent instead when your code only knows what HAPPENED and policy should decide what reacts.",
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "name": "sequence",
            "in": "path",
            "required": true,
            "description": "The sequence.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/start-sequence-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/enrollment-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/sequences/stop": {
      "post": {
        "operationId": "stopSequence",
        "summary": "Stop whatever is chasing someone.",
        "description": "Stop whatever is chasing someone. Pass the `cancelKey` you set when starting — so you cancel with your own invoice or order id and never store ours — or pass `sequence` and `email` together when you did not set one. Always answers 200 with a count, so it is safe to fire blindly from a webhook.",
        "tags": [
          "Sequences"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/stop-sequence-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/stop-sequence-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/sequences/{id}/enrollments": {
      "get": {
        "operationId": "listEnrollments",
        "summary": "List who is in a sequence and where each of them is.",
        "description": "List who is in a sequence and where each of them is. Filter with `status`.",
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Only enrollments in this state: active, waiting, completed, canceled, or failed.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows, 1-500. Omitted = 100.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/list-enrollments-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/enrollments/{id}": {
      "get": {
        "operationId": "getEnrollment",
        "summary": "Get one enrollment — which sequence, which step, and what happens next.",
        "description": "Get one enrollment — which sequence, which step, and what happens next.",
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/enrollment-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "delete": {
        "operationId": "cancelEnrollment",
        "summary": "Cancel one specific run by its enrollment id — the per-row action when you are looking at the enrollments table.",
        "description": "Cancel one specific run by its enrollment id — the per-row action when you are looking at the enrollments table. To stop whatever is chasing a contact without knowing which run, use stopSequence.",
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/cancel-enrollment-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/enrollments/{id}/runs": {
      "get": {
        "operationId": "listEnrollmentRuns",
        "summary": "Every step this enrollment has executed, with the outcome and the reason for it.",
        "description": "Every step this enrollment has executed, with the outcome and the reason for it. This is the \"why didn't step 3 fire\" view.",
        "tags": [
          "Sequences"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/list-runs-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/attachments": {
      "post": {
        "operationId": "uploadAttachment",
        "summary": "Upload a file to MailKite storage and get back a secure, time-limited URL.",
        "description": "Upload a file to MailKite storage and get back a secure, time-limited URL. Reference the returned `url` as an attachment in send() (`{ filename, url }`) or link it inline in your HTML — instead of base64-inlining large files on every send. Give the file ONE of four ways: a local `path` (read and streamed as raw bytes by the CLI/SDK/local MCP), a remote `url` (MailKite fetches and re-hosts it), base64 `content`, or — over raw HTTP — the file bytes as the POST body with `?filename=`. `retentionDays` (7/30/90/365, default 7) sets how long the file and URL live.",
        "tags": [
          "Sending"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/upload-attachment-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/upload-attachment-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/templates": {
      "get": {
        "operationId": "listTemplates",
        "summary": "List your saved email templates (light metadata only — no body).",
        "description": "List your saved email templates (light metadata only — no body). Use getTemplate for the full template.",
        "tags": [
          "Templates"
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/template-meta"
                  }
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "post": {
        "operationId": "createTemplate",
        "summary": "Create a template. Pass baseId to clone a base template into your own, or provide name/subject/html/text/theme directly.",
        "description": "Create a template. Pass `baseId` to clone a base template into your own, or provide name/subject/html/text/theme directly.",
        "tags": [
          "Templates"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/create-template-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/template"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/templates/base": {
      "get": {
        "operationId": "listBaseTemplates",
        "summary": "List the premade base templates (light metadata).",
        "description": "List the premade base templates (light metadata). Clone one with createTemplate({ baseId }) or send from it directly via send({ templateId }).",
        "tags": [
          "Templates"
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/template-meta"
                  }
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/templates/{id}": {
      "get": {
        "operationId": "getTemplate",
        "summary": "Get one template (full: subject, html, text, theme).",
        "description": "Get one template (full: subject, html, text, theme). Works for your templates (tpl_…) and base templates (base_…).",
        "tags": [
          "Templates"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/template"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/domains": {
      "get": {
        "operationId": "listDomains",
        "summary": "List your domains, each with its webhook URL.",
        "description": "List your domains, each with its webhook URL.",
        "tags": [
          "Domains"
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/domain"
                  }
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "post": {
        "operationId": "createDomain",
        "summary": "Add a domain. Returns the domain + DNS records.",
        "description": "Add a domain. Returns the domain + DNS records. Paid plans may pass `email_provider_id` to choose a provider (list available providers with listEmailProviders); free plans are always pinned to the platform default (SES US East 2 production).",
        "tags": [
          "Domains"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/create-domain-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/create-domain-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/domains/subdomain/suggest": {
      "get": {
        "operationId": "suggestSubdomain",
        "summary": "Suggest a free, currently-unclaimed subdomain label to prefill the input with, plus the base zone it would live on.",
        "description": "Suggest a free, currently-unclaimed subdomain label to prefill the input with, plus the `base` zone it would live on. Read-only — suggesting does not reserve the name. Always take the zone from `base` rather than hard-coding it: which zones are on offer changes over time.",
        "tags": [
          "Domains"
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/subdomain-suggestion"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/domains/subdomain/check": {
      "get": {
        "operationId": "checkSubdomain",
        "summary": "Check whether a free subdomain label can be claimed.",
        "description": "Check whether a free subdomain label can be claimed. Read-only and cheap — call it as the user types. `reason` explains a rejection and is safe to show verbatim.",
        "tags": [
          "Domains"
        ],
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": true,
            "description": "The name.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/subdomain-availability"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/domains/subdomain": {
      "post": {
        "operationId": "claimSubdomain",
        "summary": "Claim a free MailKite subdomain — a <label>.<base> host on a zone we run (call suggestSubdomain for the current base; the pool changes over time and more tha…",
        "description": "Claim a free MailKite subdomain — a `<label>.<base>` host on a zone we run (call suggestSubdomain for the current `base`; the pool changes over time and more than one may be offered). We publish its DNS automatically and return an empty `dns` array — nothing for the customer to publish. New claims start on SES and may remain pending while SES verifies DKIM; call verifyDomain until verified before sending. Bring your own domain with createDomain when you want mail to come from your own name.",
        "tags": [
          "Domains"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/claim-subdomain-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/claim-subdomain-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/domains/{id}": {
      "get": {
        "operationId": "getDomain",
        "summary": "Get one domain with DNS records + webhook.",
        "description": "Get one domain with DNS records + webhook.",
        "tags": [
          "Domains"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/get-domain-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "delete": {
        "operationId": "deleteDomain",
        "summary": "Remove a domain.",
        "description": "Remove a domain.",
        "tags": [
          "Domains"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/delete-domain-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/domains/{id}/verify": {
      "post": {
        "operationId": "verifyDomain",
        "summary": "Check DNS and update status.",
        "description": "Check DNS and update status.",
        "tags": [
          "Domains"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/verify-domain-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/domains/{id}/webhook": {
      "put": {
        "operationId": "setWebhook",
        "summary": "Set or replace the domain's catch-all webhook.",
        "description": "Set or replace the domain's catch-all webhook.",
        "tags": [
          "Domains"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/set-webhook-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/set-webhook-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "delete": {
        "operationId": "deleteWebhook",
        "summary": "Remove the domain's webhook.",
        "description": "Remove the domain's webhook.",
        "tags": [
          "Domains"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ok-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/domains/{id}/tracking-webhook": {
      "put": {
        "operationId": "setTrackingWebhook",
        "summary": "Set or replace the domain's dedicated tracking-event webhook: an HTTPS endpoint that receives signed email.* engagement events (email.sent / email.bounced / …",
        "description": "Set or replace the domain's dedicated tracking-event webhook: an HTTPS endpoint that receives signed email.* engagement events (email.sent / email.bounced / email.complained / email.opened / email.clicked, shaped per the tracking-event schema) SEPARATELY from inbound mail. This is the split-endpoint override for consumers that keep engagement events away from their inbound webhook (e.g. django-anymail's paired views); most consumers should prefer setWebhookEvents, which delivers everything to the one inbound webhook. When both are configured, this URL wins for engagement events. Returns the signing secret (the same account secret as inbound deliveries).",
        "tags": [
          "Domains"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/set-tracking-webhook-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/set-tracking-webhook-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "delete": {
        "operationId": "deleteTrackingWebhook",
        "summary": "Remove the domain's tracking-event webhook (engagement events stop).",
        "description": "Remove the domain's tracking-event webhook (engagement events stop).",
        "tags": [
          "Domains"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ok-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/domains/{id}/webhook-events": {
      "put": {
        "operationId": "setWebhookEvents",
        "summary": "Opt the domain's inbound webhook into engagement events — one webhook, all events.",
        "description": "Opt the domain's inbound webhook into engagement events — one webhook, all events. Pass \"all\" or a list of email.* tracking types (email.sent / email.bounced / email.complained / email.opened / email.clicked) and MailKite delivers them to the same webhook route that receives the domain's inbound mail; consumers switch on the payload's `type` (inbound mail is type email.received, engagement events follow the tracking-event schema). Off by default, so existing inbound consumers never see event types they didn't opt into. Events at the inbound webhook are signed with that route's secret (account secret fallback) — the same key inbound deliveries already use. If a dedicated tracking webhook URL is set (setTrackingWebhook), engagement events go there instead.",
        "tags": [
          "Domains"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/set-webhook-events-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/set-webhook-events-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "delete": {
        "operationId": "deleteWebhookEvents",
        "summary": "Opt the domain's inbound webhook back out of engagement events (inbound email.received only — the default).",
        "description": "Opt the domain's inbound webhook back out of engagement events (inbound email.received only — the default).",
        "tags": [
          "Domains"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ok-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/domains/{id}/webhook/secret": {
      "get": {
        "operationId": "getWebhookSecret",
        "summary": "Get this domain's webhook signing secret (whsec_…) — the per-route secret used to verify x-mailkite-signature on its inbound deliveries.",
        "description": "Get this domain's webhook signing secret (whsec_…) — the per-route secret used to verify x-mailkite-signature on its inbound deliveries. Server-side only (SDK/REST); never exposed to MCP or browser client libraries.",
        "tags": [
          "Domains"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/webhook-secret-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/domains/{id}/webhook/test": {
      "post": {
        "operationId": "testWebhook",
        "summary": "Send a signed test event to the domain's webhook.",
        "description": "Send a signed test event to the domain's webhook.",
        "tags": [
          "Domains"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/test-webhook-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/domains/register/check": {
      "get": {
        "operationId": "checkDomainAvailability",
        "summary": "Check whether a domain is available to register, and at what price.",
        "description": "Check whether a domain is available to register, and at what price. Read-only — no charge.",
        "tags": [
          "Domains"
        ],
        "parameters": [
          {
            "name": "domain",
            "in": "query",
            "required": true,
            "description": "The domain.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/domain-availability-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/domains/register": {
      "post": {
        "operationId": "registerDomain",
        "summary": "Register (buy) a domain on the customer's behalf; provisions mail DNS and adds it to the account in one call.",
        "description": "Register (buy) a domain on the customer's behalf; provisions mail DNS and adds it to the account in one call. Charges the registrar.",
        "tags": [
          "Domains"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/register-domain-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/register-domain-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/routes": {
      "get": {
        "operationId": "listRoutes",
        "summary": "List inbound routing rules.",
        "description": "List inbound routing rules.",
        "tags": [
          "Routes"
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/route"
                  }
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "post": {
        "operationId": "createRoute",
        "summary": "Create a route (match, action, destination).",
        "description": "Create a route (match, action, destination).",
        "tags": [
          "Routes"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/create-route-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/route"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/routes/{id}": {
      "delete": {
        "operationId": "deleteRoute",
        "summary": "Delete an inbound routing rule by id.",
        "description": "Delete an inbound routing rule by id. Pair with createRoute to register and tear down a webhook destination — e.g. an automation platform subscribing on enable and cleaning up on disable.",
        "tags": [
          "Routes"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ok-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/agent": {
      "post": {
        "operationId": "agent",
        "summary": "Send a message to one of your inbox agents and get its reply.",
        "description": "Send a message to one of your inbox agents and get its reply. Defaults to the account's default agent; pass `routeId` or `address` to target a specific agent, or `model` to override the model. This is separate from inbound routing — it does not match or override routes.",
        "tags": [
          "Agents & routing"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/agent-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/agent-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/route": {
      "post": {
        "operationId": "route",
        "summary": "Route a message to one of your registered routes (by routeId or address), running that route's action — agent, webhook, or forward.",
        "description": "Route a message to one of your registered routes (by `routeId` or `address`), running that route's action — agent, webhook, or forward. The route must already exist on your account; arbitrary destinations are not allowed.",
        "tags": [
          "Agents & routing"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/route-message-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/route-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/messages": {
      "get": {
        "operationId": "listMessages",
        "summary": "List stored messages, newest first.",
        "description": "List stored messages, newest first. Optionally filter with `search` (matches sender, recipient, or subject) and page with `before` (a `received_at` cursor) and `limit`; omit all for the default newest 100. Response is a bare array — paginate by passing the last row's `received_at` as the next `before`.",
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "name": "before",
            "in": "query",
            "required": false,
            "description": "Cursor: return only messages with `received_at` strictly less than this (epoch ms). Omit for the first page.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows to return, 1–200. Omitted = newest 100 (the pre-pagination default).",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match over sender, recipient, and subject. Omit for no filter.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/message"
                  }
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/messages/{id}": {
      "get": {
        "operationId": "getMessage",
        "summary": "Get a message with deliveries + attachments.",
        "description": "Get a message with deliveries + attachments.",
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/message-detail"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "delete": {
        "operationId": "deleteMessage",
        "summary": "Soft-delete one stored message owned by your account.",
        "description": "Soft-delete one stored message owned by your account. Hides it from normal message, conversation and mailbox reads while retaining content, attachments and delivery evidence. Does not recall delivered mail or reduce metered usage. Repeated deletion returns the original deleted_at timestamp; unknown or foreign ids return 404.",
        "tags": [
          "Messages"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/delete-message-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/deliveries/{id}/retry": {
      "post": {
        "operationId": "retryDelivery",
        "summary": "Re-deliver a stored message to its webhook.",
        "description": "Re-deliver a stored message to its webhook.",
        "tags": [
          "Deliveries"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/retry-delivery-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/deliveries/retry": {
      "post": {
        "operationId": "retryDeliveries",
        "summary": "Replay a whole selection of webhook deliveries in one call — the bulk form of retryDelivery.",
        "description": "Replay a whole selection of webhook deliveries in one call — the bulk form of retryDelivery. Pass `deliveryIds` (replay those exact deliveries), `messageIds` (replay each message's most recent delivery per route), and/or `threadIds` (expanded server-side to every message in the conversation); they combine. At most 50 ids per request, so send larger selections as sequential batches. Always answers 200 with a per-id `results` array — one unreachable endpoint never hides the outcomes of the rest — so branch on `ok` and `results`, not the HTTP status. An id you don't own is reported as skipped with reason `not_found`, exactly as the single-delivery endpoint 404s.",
        "tags": [
          "Deliveries"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/replay-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/replay-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/deliveries/{id}/attempts": {
      "get": {
        "operationId": "listDeliveryAttempts",
        "summary": "Every captured attempt for one delivery, newest first: the request headers and payload we POSTed, and the status, headers, and body that came back.",
        "description": "Every captured attempt for one delivery, newest first: the request headers and payload we POSTed, and the status, headers, and body that came back. This is the per-attempt record behind a webhook post-mortem, fetched without pulling the whole message. Captures are retained for 45 days.",
        "tags": [
          "Deliveries"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/delivery-attempts-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/routes/{id}/deliver": {
      "post": {
        "operationId": "deliverToRoute",
        "summary": "POST stored messages to one webhook route — including messages that arrived BEFORE the route existed, which no retry can reach (a retry replays an existing d…",
        "description": "POST stored messages to one webhook route — including messages that arrived BEFORE the route existed, which no retry can reach (a retry replays an existing delivery row, and a new route has none). Use listRouteCandidates to find what a route could be sent. Webhook routes only: a forward would re-send real mail to a third party and an agent would re-spend model tokens, so both answer 400 `route_action`. At most 50 ids per request; same per-id `results` contract as retryDeliveries.",
        "tags": [
          "Routes"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/deliver-to-route-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/replay-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/routes/{id}/candidates": {
      "get": {
        "operationId": "listRouteCandidates",
        "summary": "Stored inbound messages this route could be asked to deliver, newest first — the preview for replaying mail to a route defined after that mail arrived.",
        "description": "Stored inbound messages this route could be asked to deliver, newest first — the preview for replaying mail to a route defined after that mail arrived. Each row carries `delivered_here`, so you can send a route only what it has never seen. Page with `before` (the response's `nextBefore`); because matching happens after a bounded scan, a page can come back shorter than `limit` while more still remain — keep going until `nextBefore` is null.",
        "tags": [
          "Routes"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "before",
            "in": "query",
            "required": false,
            "description": "Cursor: scan only messages with `received_at` strictly less than this (epoch ms). Pass the previous response's `nextBefore`. Omit for the first page.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max matching rows to return, 1–100. Omitted = 50.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/route-candidates-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/realtime/token": {
      "post": {
        "operationId": "createRealtimeToken",
        "summary": "Mint a short-lived, single-use token that authorises one Realtime API connection.",
        "description": "Mint a short-lived, single-use token that authorises one Realtime API connection. For browsers: EventSource cannot set headers, so this is what a page passes as ?token= instead of putting an API key in a URL. Inherits the calling credential's scope, expires in five minutes, and burns on first use. The stream itself is GET /v1/realtime — a subscription, not a request/response call, so it is contracted in sdks/spec/realtime.json rather than here.",
        "tags": [
          "Realtime"
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/realtime-token-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/lists": {
      "get": {
        "operationId": "listLists",
        "summary": "List your contact lists (static, curated broadcast audiences), each with its member count.",
        "description": "List your contact lists (static, curated broadcast audiences), each with its member count.",
        "tags": [
          "Lists"
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/contact-list"
                  }
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "post": {
        "operationId": "createList",
        "summary": "Create a contact list.",
        "description": "Create a contact list. Returns the list with its id (lst_…); add contacts with addListContacts.",
        "tags": [
          "Lists"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/create-list-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/contact-list"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/lists/{id}": {
      "get": {
        "operationId": "getList",
        "summary": "Get one contact list with its member count.",
        "description": "Get one contact list with its member count.",
        "tags": [
          "Lists"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/contact-list"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "patch": {
        "operationId": "updateList",
        "summary": "Rename a contact list.",
        "description": "Rename a contact list.",
        "tags": [
          "Lists"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/update-list-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/contact-list"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "delete": {
        "operationId": "deleteList",
        "summary": "Delete a contact list.",
        "description": "Delete a contact list. The list is removed; the contacts themselves are kept.",
        "tags": [
          "Lists"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ok-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/lists/{id}/contacts": {
      "get": {
        "operationId": "listListContacts",
        "summary": "List the contacts that are members of a list, newest first.",
        "description": "List the contacts that are members of a list, newest first. Optionally page with `before` (a `last_seen_at`/`created_at` cursor) and `limit`. Response is a bare array — paginate by passing the last row's `last_seen_at` (or `created_at`) as the next `before`.",
        "tags": [
          "Lists"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "before",
            "in": "query",
            "required": false,
            "description": "Cursor: return only members with `last_seen_at` (or `created_at`) strictly less than this (epoch ms). Omit for the first page.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows to return (default 100, max 500).",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/contact"
                  }
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "post": {
        "operationId": "addListContacts",
        "summary": "Add contacts (by id, ctr_…) to a list.",
        "description": "Add contacts (by id, ctr_…) to a list. Returns how many were newly added; contacts already on the list are ignored.",
        "tags": [
          "Lists"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/add-list-contacts-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/add-list-contacts-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/lists/{id}/contacts/{contactId}": {
      "delete": {
        "operationId": "removeListContact",
        "summary": "Remove one contact from a list (the contact itself is kept).",
        "description": "Remove one contact from a list (the contact itself is kept).",
        "tags": [
          "Lists"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "contactId",
            "in": "path",
            "required": true,
            "description": "The contactId.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ok-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/broadcasts": {
      "get": {
        "operationId": "listBroadcasts",
        "summary": "List your broadcasts (one-to-many sends) with status and send stats.",
        "description": "List your broadcasts (one-to-many sends) with status and send stats.",
        "tags": [
          "Broadcasts"
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/broadcast"
                  }
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "post": {
        "operationId": "createBroadcast",
        "summary": "Create a broadcast draft.",
        "description": "Create a broadcast draft. `from` is required; set `audience` to { type: \"all\" } or { type: \"list\", id: \"lst_…\" }. Returns the broadcast with its id (bct_…). Send it with sendBroadcast.",
        "tags": [
          "Broadcasts"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/create-broadcast-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/broadcast"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/broadcasts/{id}": {
      "get": {
        "operationId": "getBroadcast",
        "summary": "Get one broadcast with its status and recipient summary.",
        "description": "Get one broadcast with its status and recipient summary.",
        "tags": [
          "Broadcasts"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/broadcast"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "patch": {
        "operationId": "updateBroadcast",
        "summary": "Edit a draft broadcast (any of from/subject/audience/html/… ).",
        "description": "Edit a draft broadcast (any of from/subject/audience/html/… ). Drafts only.",
        "tags": [
          "Broadcasts"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/update-broadcast-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/broadcast"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "delete": {
        "operationId": "deleteBroadcast",
        "summary": "Delete a broadcast draft.",
        "description": "Delete a broadcast draft.",
        "tags": [
          "Broadcasts"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ok-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/broadcasts/{id}/send": {
      "post": {
        "operationId": "sendBroadcast",
        "summary": "Send a broadcast now, or pass an ISO 8601 scheduledAt to schedule it.",
        "description": "Send a broadcast now, or pass an ISO 8601 `scheduledAt` to schedule it. A one-click unsubscribe is always added. Returns the status and resolved audience count.",
        "tags": [
          "Broadcasts"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/send-broadcast-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/send-broadcast-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/docs/search": {
      "get": {
        "operationId": "semanticSearch",
        "summary": "Semantic search over the MailKite documentation — returns the most relevant doc sections for a natural-language query (hybrid vector + keyword search over ht…",
        "description": "Semantic search over the MailKite documentation — returns the most relevant doc sections for a natural-language query (hybrid vector + keyword search over https://mailkite.dev/docs). Public; no authentication required.",
        "tags": [
          "Docs"
        ],
        "security": [],
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": true,
            "description": "The query.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/semantic-search-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/domains/{id}/retention": {
      "put": {
        "operationId": "setRetention",
        "summary": "Toggle zero-retention passthrough for the domain ({ zeroRetention }).",
        "description": "Toggle zero-retention passthrough for the domain ({ zeroRetention }). With it on, message bodies are relayed to your webhook and never stored.",
        "tags": [
          "Domains"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/set-retention-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/domain-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/domains/{id}/encryption": {
      "put": {
        "operationId": "setEncryption",
        "summary": "Enable at-rest encryption for the domain with a public key ({ publicKey }).",
        "description": "Enable at-rest encryption for the domain with a public key ({ publicKey }). Stored bodies are encrypted to it; only the holder of the private key can read them back.",
        "tags": [
          "Domains"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/set-encryption-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/domain-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "delete": {
        "operationId": "deleteEncryption",
        "summary": "Disable at-rest encryption for the domain.",
        "description": "Disable at-rest encryption for the domain. Messages stored while it was on stay encrypted.",
        "tags": [
          "Domains"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/domain-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/schemas": {
      "get": {
        "operationId": "listSchemas",
        "summary": "List every published JSON Schema with the URL it is served from.",
        "description": "List every published JSON Schema with the URL it is served from.",
        "tags": [
          "Schemas"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/list-schemas-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/schemas/{name}": {
      "get": {
        "operationId": "getSchema",
        "summary": "Fetch one schema as application/schema+json, with an absolute $id a validator can resolve and cache.",
        "description": "Fetch one schema as application/schema+json, with an absolute $id a validator can resolve and cache.",
        "tags": [
          "Schemas"
        ],
        "security": [],
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "description": "The name.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/schema-document"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/auth/signup": {
      "post": {
        "operationId": "signup",
        "summary": "Create an account. Returns a session token.",
        "description": "Create an account. Returns a session token.",
        "tags": [
          "Auth"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/signup-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/auth-token-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/auth/login": {
      "post": {
        "operationId": "login",
        "summary": "Log in with email + password.",
        "description": "Log in with email + password. Returns a session token.",
        "tags": [
          "Auth"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/login-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/auth-token-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/auth/google": {
      "post": {
        "operationId": "googleSignin",
        "summary": "Sign in with a Google auth code.",
        "description": "Sign in with a Google auth code. Returns a session token.",
        "tags": [
          "Auth"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/google-auth-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/auth-token-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "operationId": "oauthMetadata",
        "summary": "Discovery document for MailKite's authorization server (RFC 8414).",
        "description": "Discovery document for MailKite's authorization server (RFC 8414). Read the authorize/token/register endpoints from here instead of hard-coding them. Also served at /.well-known/openid-configuration.",
        "tags": [
          "Account linking"
        ],
        "security": [],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/oauth-metadata-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/oauth/register": {
      "post": {
        "operationId": "registerOauthClient",
        "summary": "Register an OAuth client for this installation (RFC 7591 dynamic client registration) — step 1 of linking an existing MailKite account to your app.",
        "description": "Register an OAuth client for this installation (RFC 7591 dynamic client registration) — step 1 of linking an existing MailKite account to your app. No pre-shared secret and no manual app review: you get a client_id back immediately and prove yourself with PKCE. Register once per install and keep the client_id.",
        "tags": [
          "Account linking"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/oauth-client-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/oauth-client-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "operationId": "exchangeOauthToken",
        "summary": "Exchange an authorization code for an access token (or rotate a refresh token) — step 3 of linking.",
        "description": "Exchange an authorization code for an access token (or rotate a refresh token) — step 3 of linking. Between steps you send the user's browser to /oauth/authorize with your client_id, redirect_uri, state, and an S256 code_challenge; they sign in with whatever method they already use and approve, and the code comes back to your redirect_uri. Then call getApiKey with the access token and store the key.",
        "tags": [
          "Account linking"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/oauth-token-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/oauth-token-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/keys": {
      "get": {
        "operationId": "getApiKey",
        "summary": "Get the account's unrestricted API key (mk_live_…).",
        "description": "Get the account's unrestricted API key (mk_live_…). Read-or-create: the first call mints it.",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/api-key-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/keys/rotate": {
      "post": {
        "operationId": "rotateApiKey",
        "summary": "Rotate the account API key: the old key stops working immediately and a fresh one is returned.",
        "description": "Rotate the account API key: the old key stops working immediately and a fresh one is returned. The plaintext is only shown here.",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/api-key-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/keys/scoped": {
      "get": {
        "operationId": "listScopedKeys",
        "summary": "List the account's domain-scoped API keys.",
        "description": "List the account's domain-scoped API keys. A scoped key can send and manage only its one domain — hand one to each site or CI job so a leak burns only that surface.",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/scoped-key"
                  }
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "post": {
        "operationId": "createScopedKey",
        "summary": "Create a key scoped to one domain.",
        "description": "Create a key scoped to one domain. Ideal for per-site installs (e.g. a WordPress plugin) — the site never holds the account master key.",
        "tags": [
          "Account"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/create-scoped-key-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/scoped-key"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/keys/scoped/{id}": {
      "delete": {
        "operationId": "deleteScopedKey",
        "summary": "Revoke a domain-scoped key.",
        "description": "Revoke a domain-scoped key. Takes effect immediately.",
        "tags": [
          "Account"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ok-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/app-passwords": {
      "get": {
        "operationId": "listAppPasswords",
        "summary": "List the account's app passwords.",
        "description": "List the account's app passwords. Each one opens a mailbox over IMAP and/or the mailbox API, scoped to a domain and an address pattern within it.",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/app-password"
                  }
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "post": {
        "operationId": "createAppPassword",
        "summary": "Create an app password for one domain and address pattern.",
        "description": "Create an app password for one domain and address pattern. Hand it to a mail client or an agent — the secret is returned once and never again.",
        "tags": [
          "Account"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/create-app-password-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/app-password"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/app-passwords/{id}": {
      "patch": {
        "operationId": "updateAppPassword",
        "summary": "Change what an app password covers — its label, address pattern, or protocols.",
        "description": "Change what an app password covers — its label, address pattern, or protocols. The domain is fixed for the life of the password: repointing a live credential would hand its holder mail they were never granted, so create a new password instead.",
        "tags": [
          "Account"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/update-app-password-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/app-password"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "delete": {
        "operationId": "deleteAppPassword",
        "summary": "Revoke an app password.",
        "description": "Revoke an app password. Takes effect immediately — any IMAP session or API call using it stops authenticating.",
        "tags": [
          "Account"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ok-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/app-passwords/{id}/rotate": {
      "post": {
        "operationId": "rotateAppPassword",
        "summary": "Replace an app password's secret, keeping its scope.",
        "description": "Replace an app password's secret, keeping its scope. The old secret stops authenticating immediately; the new one is returned once. Use after a leak, when revoking would mean reconfiguring every client.",
        "tags": [
          "Account"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "The id.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/app-password"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/mailbox/messages": {
      "get": {
        "operationId": "listMailboxMessages",
        "summary": "List a mailbox's messages, newest first.",
        "description": "List a mailbox's messages, newest first. Authenticated with an app password granting `api` — this is how an agent reads its own inbox without an IMAP client.",
        "tags": [
          "Mailbox"
        ],
        "parameters": [
          {
            "name": "address",
            "in": "query",
            "required": true,
            "description": "The address.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "before",
            "in": "query",
            "required": false,
            "description": "Cursor: return only messages with a UID strictly less than this. Omit for the first page.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max messages to return, 1–200. Omitted = 50.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/mailbox-messages-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/mailbox/messages/{uid}/raw": {
      "get": {
        "operationId": "getMailboxMessageRaw",
        "summary": "Fetch one message's raw RFC822 bytes from a mailbox.",
        "description": "Fetch one message's raw RFC822 bytes from a mailbox. Same app password auth as the list.",
        "tags": [
          "Mailbox"
        ],
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "description": "The uid.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "address",
            "in": "query",
            "required": true,
            "description": "The address.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/mailbox/messages/{uid}/flags": {
      "post": {
        "operationId": "setMailboxMessageFlags",
        "summary": "Replace a message's IMAP flags (e.g.",
        "description": "Replace a message's IMAP flags (e.g. mark it `Seen`). Flags set here are the same ones an IMAP client sees.",
        "tags": [
          "Mailbox"
        ],
        "parameters": [
          {
            "name": "uid",
            "in": "path",
            "required": true,
            "description": "The uid.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "address",
            "in": "query",
            "required": true,
            "description": "The address.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/set-mailbox-flags-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ok-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/billing/usage": {
      "get": {
        "operationId": "getUsage",
        "summary": "Current billing-period usage: emails used vs the plan's included bucket (null = unlimited), AI actions, and the overage state that gates sending.",
        "description": "Current billing-period usage: emails used vs the plan's included bucket (null = unlimited), AI actions, and the overage state that gates sending. Powers quota meters in dashboards and integrations.",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/usage-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/contacts/suppressions": {
      "get": {
        "operationId": "listSuppressions",
        "summary": "List suppressed addresses (unsubscribes, hard bounces, spam complaints, manual).",
        "description": "List suppressed addresses (unsubscribes, hard bounces, spam complaints, manual). Sends to a suppressed address are dropped before delivery.",
        "tags": [
          "Suppressions"
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/suppressions-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      },
      "post": {
        "operationId": "addSuppression",
        "summary": "Suppress an address so this account never sends to it again (reason defaults to manual).",
        "description": "Suppress an address so this account never sends to it again (reason defaults to manual).",
        "tags": [
          "Suppressions"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/add-suppression-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/add-suppression-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/contacts/suppressions/{email}": {
      "delete": {
        "operationId": "removeSuppression",
        "summary": "Remove an address from the suppression list (URL-encode the email in the path).",
        "description": "Remove an address from the suppression list (URL-encode the email in the path). Removing an unsuppressed address is a no-op success.",
        "tags": [
          "Suppressions"
        ],
        "parameters": [
          {
            "name": "email",
            "in": "path",
            "required": true,
            "description": "The email.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/remove-suppression-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/api/v1/provision": {
      "post": {
        "operationId": "register",
        "summary": "Create a MailKite account from just an email — no password.",
        "description": "Create a MailKite account from just an email — no password. Returns the new account's API key immediately; a verification link is emailed, and SENDING stays blocked until the address is verified (poll me()). An existing email returns 409 account_exists with no credentials. Powers plugin/CLI onboarding.",
        "tags": [
          "Account"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/register-request"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/register-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    },
    "/v1/me": {
      "get": {
        "operationId": "me",
        "summary": "The account behind this credential: email, whether it is verified (sending is blocked until it is), and plan.",
        "description": "The account behind this credential: email, whether it is verified (sending is blocked until it is), and plan. Use to poll verification state after register().",
        "tags": [
          "Account"
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/me-response"
                }
              }
            }
          },
          "4XX": {
            "description": "The request was rejected; the body carries `error` and usually `code`."
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "A MailKite API key (mk_live_…), a session token, an OAuth 2.1 access token from mcp.mailkite.dev, or an mk_imap_ app password — all interchangeable as `Authorization: Bearer <token>`."
      }
    },
    "schemas": {
      "add-list-contacts-request": {
        "title": "Add contacts to list request body",
        "type": "object",
        "required": [
          "contactIds"
        ],
        "additionalProperties": false,
        "properties": {
          "contactIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "add-list-contacts-response": {
        "title": "Add list contacts response",
        "description": "POST /api/lists/:id/contacts — how many contacts were actually added (already-present members don't count twice).",
        "type": "object",
        "required": [
          "added"
        ],
        "properties": {
          "added": {
            "type": "integer",
            "description": "Contacts newly added to the list.",
            "examples": [
              3
            ]
          }
        }
      },
      "add-suppression-request": {
        "title": "Add suppression request body",
        "type": "object",
        "required": [
          "email"
        ],
        "additionalProperties": false,
        "properties": {
          "email": {
            "type": "string",
            "description": "The address to stop sending to.",
            "examples": [
              "unsubscribed@example.com"
            ]
          },
          "reason": {
            "type": "string",
            "enum": [
              "unsubscribe",
              "hard_bounce",
              "spam_complaint",
              "manual"
            ],
            "description": "Why — defaults to manual when omitted or unrecognized.",
            "examples": [
              "unsubscribe"
            ]
          },
          "note": {
            "type": "string",
            "description": "Optional free-text note (who/why), shown alongside the entry.",
            "examples": [
              "customer asked via support ticket #482"
            ]
          }
        }
      },
      "add-suppression-response": {
        "title": "Add suppression result",
        "type": "object",
        "required": [
          "suppressed",
          "email"
        ],
        "additionalProperties": false,
        "properties": {
          "suppressed": {
            "type": "boolean",
            "description": "Always true on success.",
            "examples": [
              true
            ]
          },
          "email": {
            "type": "string",
            "description": "The normalized address that was suppressed.",
            "examples": [
              "unsubscribed@example.com"
            ]
          }
        }
      },
      "agent-request": {
        "title": "Send-to-agent request body",
        "type": "object",
        "required": [
          "text"
        ],
        "additionalProperties": false,
        "properties": {
          "text": {
            "type": "string",
            "description": "The message for the agent — it reads this as the incoming email body and decides what to do."
          },
          "subject": {
            "type": "string",
            "description": "Optional subject line the agent sees on the message."
          },
          "from": {
            "type": "string",
            "description": "Optional sender address the agent sees as the originator. Defaults to the account's API caller address."
          },
          "html": {
            "type": "string",
            "description": "Optional HTML body. `text` is still required — the agent reasons over the plain-text content."
          },
          "routeId": {
            "type": "string",
            "description": "Target a specific agent by its route id (rte_…). Omit to use the account's default agent (its most recently created agent route)."
          },
          "address": {
            "type": "string",
            "description": "Target the agent whose route matches this address. Alternative to routeId."
          },
          "model": {
            "type": "string",
            "description": "Override the model the agent runs on for this call (e.g. claude-sonnet-4-6)."
          }
        }
      },
      "agent-response": {
        "title": "Send-to-agent response body",
        "type": "object",
        "required": [
          "ok"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "description": "Whether the agent ran to completion."
          },
          "text": {
            "type": "string",
            "description": "The agent's reply — its final message after running."
          },
          "messageId": {
            "type": "string",
            "description": "Id of the stored message the agent processed (msg_…)."
          },
          "error": {
            "type": "string",
            "description": "Error detail when `ok` is false."
          }
        }
      },
      "api-key-response": {
        "title": "Account API key",
        "type": "object",
        "required": [
          "key"
        ],
        "additionalProperties": false,
        "properties": {
          "key": {
            "type": "string",
            "description": "The account's unrestricted API key (mk_live_…). Read-or-create: the first call mints it. rotate() invalidates the old key and returns a fresh one.",
            "examples": [
              "mk_live_9hK2mQx7Tw4bVnR8"
            ]
          }
        }
      },
      "app-password": {
        "title": "App password",
        "description": "A credential that opens a mailbox over IMAP and/or the mailbox API. Scoped to one domain and an address pattern within it, so a single password can serve every address on a domain or exactly one. The secret itself is only ever returned once, at creation.",
        "type": "object",
        "required": [
          "id",
          "user_id",
          "label",
          "domain",
          "address",
          "protocols",
          "key_prefix",
          "created_at",
          "last_used_at",
          "legacy"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "description": "App password id (apw_…).",
            "examples": [
              "apw_6Kd3PqR7"
            ]
          },
          "user_id": {
            "type": "string",
            "description": "Owning account (usr_…).",
            "examples": [
              "usr_7Fj3MnQw"
            ]
          },
          "label": {
            "type": "string",
            "description": "User-supplied name, e.g. which client or agent holds it.",
            "examples": [
              "support inbox agent"
            ]
          },
          "domain_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The hosted domain this password covers (dom_…). Null only for legacy account-wide credentials.",
            "examples": [
              "dom_2VbXqTpN8rKw"
            ]
          },
          "domain": {
            "type": "string",
            "description": "Domain name this password covers. The literal `*` marks a legacy account-wide IMAP credential, which covers every domain the account owns; new passwords always name one domain.",
            "examples": [
              "acme.dev"
            ]
          },
          "address": {
            "type": "string",
            "description": "Local-part pattern within the domain: `*` (every address), `hello` (exact), `support-*` / `*-agent` (glob). Same wildcard grammar as inbound routes.",
            "examples": [
              "support-*"
            ]
          },
          "protocols": {
            "type": "array",
            "description": "What this password may authenticate: `imap` for IMAP LOGIN, `api` for Bearer auth on the mailbox API. At least one.",
            "items": {
              "type": "string",
              "enum": [
                "imap",
                "api"
              ]
            },
            "examples": [
              [
                "imap",
                "api"
              ]
            ]
          },
          "key_prefix": {
            "type": "string",
            "description": "First characters of the secret, for display (the rest is never retrievable).",
            "examples": [
              "mk_pw_9tQ2xVb"
            ]
          },
          "created_at": {
            "type": "number",
            "description": "Creation time, ms epoch.",
            "examples": [
              1753900000000
            ]
          },
          "last_used_at": {
            "type": [
              "number",
              "null"
            ],
            "description": "Last authenticated use, ms epoch — null if never used.",
            "examples": [
              null
            ]
          },
          "legacy": {
            "type": "boolean",
            "description": "True for pre-existing IMAP-only credentials (mk_imap_…) surfaced in this shape. They keep working indefinitely but cannot grant `api`.",
            "examples": [
              false
            ]
          },
          "secret": {
            "type": "string",
            "description": "The password itself (mk_pw_…). Returned ONLY in the create response — store it then, it is never shown again.",
            "examples": [
              "mk_pw_9tQ2xVb4Nm8kLc3Wd7Rf1Zy6Hs0Ap5Ej"
            ]
          }
        }
      },
      "auth-token-response": {
        "title": "Auth token response",
        "description": "Signup, login, and OAuth sign-in all resolve to this: a session token plus the account it belongs to. Send the token as `Authorization: Bearer <token>` on /api/* calls.",
        "type": "object",
        "required": [
          "token",
          "user"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "Session JWT.",
            "examples": [
              "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9…"
            ]
          },
          "user": {
            "type": "object",
            "required": [
              "id",
              "email",
              "isAdmin",
              "emailVerified"
            ],
            "description": "The signed-in account.",
            "properties": {
              "id": {
                "type": "string",
                "description": "Account id (usr_…).",
                "examples": [
                  "usr_7Fj3MnQw"
                ]
              },
              "email": {
                "type": "string",
                "description": "Account email.",
                "examples": [
                  "ada@example.com"
                ]
              },
              "isAdmin": {
                "type": "boolean",
                "description": "True for MailKite staff accounts."
              },
              "emailVerified": {
                "type": "boolean",
                "description": "Whether the account email has been confirmed."
              },
              "avatarUrl": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Avatar image URL, when the identity provider supplied one."
              }
            }
          }
        }
      },
      "batch-send-request": {
        "title": "Batch send request body",
        "description": "Send one personalized message per recipient in a single call. Shared fields (subject/html/text/templateId/templateData/headers/…) form the base message; each recipients[] entry produces an independent message to exactly one address, with its own templateData and headers merged over the shared ones (per-recipient wins). Each message passes the same gates as a single send() and gets its own id — recipients never see each other.",
        "type": "object",
        "required": [
          "from",
          "recipients"
        ],
        "additionalProperties": false,
        "properties": {
          "from": {
            "type": "string",
            "description": "An address on a verified domain. Shared by every message in the batch."
          },
          "recipients": {
            "type": "array",
            "description": "One entry per message. Order is preserved in the response's results[].",
            "minItems": 1,
            "maxItems": 50,
            "items": {
              "type": "object",
              "required": [
                "to"
              ],
              "additionalProperties": false,
              "properties": {
                "to": {
                  "type": "string",
                  "description": "Exactly one recipient address (bare or `Name <addr>`). This message is sent to this address only.",
                  "examples": [
                    "Ada Lovelace <ada@example.com>"
                  ]
                },
                "templateData": {
                  "type": "object",
                  "description": "Per-recipient merge values, merged over the shared templateData (this wins on conflicts). Fills {{merge_tags}} in the subject/html/text for this recipient's message.",
                  "additionalProperties": {
                    "type": [
                      "string",
                      "number",
                      "boolean",
                      "null"
                    ]
                  }
                },
                "headers": {
                  "type": "object",
                  "description": "Per-recipient extra raw MIME headers, merged over the shared headers (this wins on conflicts).",
                  "additionalProperties": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "subject": {
            "type": "string",
            "description": "Required unless supplied by a template. May contain {{merge_tags}}, filled per recipient."
          },
          "html": {
            "type": "string",
            "description": "May contain {{merge_tags}}, filled per recipient."
          },
          "text": {
            "type": "string",
            "description": "May contain {{merge_tags}}, filled per recipient."
          },
          "templateId": {
            "type": "string",
            "description": "Send using a saved template — a user template (tpl_…) or a base template (base_…). Its subject/html/text seed every message; explicit subject/html/text here override them."
          },
          "templateData": {
            "type": "object",
            "description": "Shared default merge values for every recipient; a recipient's own templateData overrides these key-by-key. HTML values are auto-escaped.",
            "additionalProperties": {
              "type": [
                "string",
                "number",
                "boolean",
                "null"
              ]
            }
          },
          "headers": {
            "type": "object",
            "description": "Shared extra raw MIME headers for every message, applied after threading headers (caller wins); a recipient's own headers override these key-by-key.",
            "additionalProperties": {
              "type": "string"
            }
          },
          "replyTo": {
            "type": "string"
          },
          "inReplyTo": {
            "type": "string",
            "description": "Thread every message under this Message-ID."
          },
          "attachments": {
            "type": "array",
            "description": "Attached to every message in the batch. Same shape as send()'s attachments.",
            "items": {
              "type": "object",
              "required": [
                "filename"
              ],
              "properties": {
                "filename": {
                  "type": "string"
                },
                "url": {
                  "type": "string",
                  "description": "Fetch the file from this URL at send time and attach it. Any URL works; an uploadAttachment() URL is the secure, recommended choice. Prefer this over `content` for large files."
                },
                "content": {
                  "type": "string",
                  "description": "Inline file bytes as base64. Simple for tiny files, but re-uploaded on every send — use `url` (see uploadAttachment) for anything large."
                },
                "contentType": {
                  "type": "string",
                  "description": "MIME type; defaults to application/octet-stream."
                }
              }
            }
          },
          "scheduledAt": {
            "type": [
              "string",
              "number"
            ],
            "description": "Send later: ISO 8601, simple relative natural language (\"in 2 hours\"), or a ms-epoch. A future time parks every message with the scheduler (each gets its own ssnd_… id, individually cancelable); omitted or past sends now."
          },
          "trackOpens": {
            "type": "boolean",
            "description": "Open-tracking override for every message in the batch (HTML only). Omitted → the from-domain's default applies."
          },
          "trackClicks": {
            "type": "boolean",
            "description": "Click-tracking override for every message in the batch (HTML only): http(s) links are rewritten to a signed redirect that records the click, then 302s to the destination. Omitted → the from-domain's default applies."
          }
        }
      },
      "batch-send-response": {
        "title": "Batch send response body",
        "description": "Per-recipient outcomes, in the same order as the request's recipients[]. The call returns 200 whenever the batch itself was accepted — check each result's status: a batch can partially succeed (e.g. one address suppressed, the rest sent). Every non-failed result carries its own message id.",
        "type": "object",
        "required": [
          "results",
          "sent",
          "scheduled",
          "failed"
        ],
        "properties": {
          "results": {
            "type": "array",
            "description": "One entry per recipients[] entry, same order.",
            "items": {
              "type": "object",
              "required": [
                "to",
                "status"
              ],
              "properties": {
                "to": {
                  "type": "string",
                  "description": "The recipient this result is for (echoed from the request)."
                },
                "id": {
                  "type": "string",
                  "description": "The message id (msg_…), or the scheduled-send id (ssnd_…) when parked for later. Absent on failure."
                },
                "status": {
                  "type": "string",
                  "description": "sent | scheduled | failed.",
                  "examples": [
                    "sent"
                  ]
                },
                "error": {
                  "type": "string",
                  "description": "Human-readable reason, present only when status is failed."
                },
                "code": {
                  "type": "string",
                  "description": "Machine-readable reason (e.g. recipient_suppressed, daily_limit), present only when status is failed."
                }
              }
            }
          },
          "sent": {
            "type": "integer",
            "description": "Count of results with status sent."
          },
          "scheduled": {
            "type": "integer",
            "description": "Count of results with status scheduled."
          },
          "failed": {
            "type": "integer",
            "description": "Count of results with status failed."
          }
        }
      },
      "broadcast": {
        "title": "Broadcast",
        "description": "One broadcast (bct_…): content + audience + send state. Only drafts are editable; send() flips a draft to 'scheduled' or 'sending' and the per-recipient ledger tracks fan-out.",
        "type": "object",
        "required": [
          "id",
          "user_id",
          "name",
          "from_addr",
          "subject",
          "audience",
          "status",
          "sent_count",
          "fail_count",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Broadcast id (bct_…).",
            "examples": [
              "bct_3Jf7HkPw"
            ]
          },
          "user_id": {
            "type": "string",
            "description": "Owning account (usr_…).",
            "examples": [
              "usr_7Fj3MnQw"
            ]
          },
          "name": {
            "type": "string",
            "description": "Internal name shown in the dashboard list.",
            "examples": [
              "July product update"
            ]
          },
          "from_addr": {
            "type": "string",
            "description": "Sender, on an outbound-verified domain you own. May carry a display name.",
            "examples": [
              "MailKite <hello@myapp.ai>"
            ]
          },
          "reply_to": {
            "type": [
              "string",
              "null"
            ],
            "description": "Reply-To address, when set."
          },
          "subject": {
            "type": "string",
            "description": "Subject line.",
            "examples": [
              "What's new in July"
            ]
          },
          "preview": {
            "type": [
              "string",
              "null"
            ],
            "description": "Inbox preview text (the snippet next to the subject)."
          },
          "audience": {
            "type": "string",
            "description": "Audience selection as a JSON string: { \"type\": \"all\" | \"list\" | \"filter\", \"id\"?: … }.",
            "examples": [
              "{\"type\":\"list\",\"id\":\"lst_5Wd9JqRs\"}"
            ]
          },
          "template_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Template the content comes from (tpl_… / base_…), when content isn't inline."
          },
          "html": {
            "type": [
              "string",
              "null"
            ],
            "description": "Inline HTML body, when not using a template."
          },
          "text": {
            "type": [
              "string",
              "null"
            ],
            "description": "Inline plain-text body, when not using a template."
          },
          "footer_address": {
            "type": [
              "string",
              "null"
            ],
            "description": "Physical postal address for the CAN-SPAM footer. Prefilled from the account when not set."
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "scheduled",
              "sending",
              "sent",
              "canceled",
              "failed"
            ],
            "description": "Send lifecycle state."
          },
          "scheduled_at": {
            "type": [
              "integer",
              "null"
            ],
            "description": "When a scheduled broadcast will send, in Unix epoch milliseconds (UTC); null when it has never happened.",
            "examples": [
              1769731200000
            ]
          },
          "audience_count": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Snapshot of the audience size taken at send/schedule time; null for drafts.",
            "examples": [
              1284
            ]
          },
          "sent_count": {
            "type": "integer",
            "description": "Recipients sent so far.",
            "examples": [
              0
            ]
          },
          "fail_count": {
            "type": "integer",
            "description": "Recipients failed so far.",
            "examples": [
              0
            ]
          },
          "sent_at": {
            "type": [
              "integer",
              "null"
            ],
            "description": "When sending completed, in Unix epoch milliseconds (UTC); null when it has never happened.",
            "examples": [
              1769731200000
            ]
          },
          "created_at": {
            "type": "integer",
            "description": "When the broadcast was created, in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731200000
            ]
          },
          "updated_at": {
            "type": "integer",
            "description": "When the broadcast last changed, in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731200000
            ]
          }
        }
      },
      "cancel-enrollment-response": {
        "title": "Cancel enrollment response body",
        "type": "object",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "enr_5b2d9f01"
            ]
          },
          "status": {
            "type": "string",
            "examples": [
              "canceled"
            ]
          }
        }
      },
      "claim-subdomain-request": {
        "title": "Claim subdomain request body",
        "description": "POST /api/domains/subdomain — the label to claim on the free pool zone.",
        "type": "object",
        "required": [
          "subdomain"
        ],
        "additionalProperties": false,
        "properties": {
          "subdomain": {
            "type": "string",
            "description": "The label to claim: 3–32 characters, lowercase letters, digits and hyphens, no leading or trailing hyphen, and not a reserved word. Just the label — a full hostname (`<label>.<base>`) is also accepted and the base stripped.",
            "examples": [
              "swift-otter"
            ]
          }
        }
      },
      "claim-subdomain-response": {
        "title": "Claim subdomain response",
        "description": "POST /api/domains/subdomain — the claimed subdomain, already verified. Unlike a domain you bring yourself, the pool zone covers this hostname entirely: `dns` is an empty array because there is nothing for you to publish.",
        "type": "object",
        "required": [
          "domain",
          "dns"
        ],
        "properties": {
          "domain": {
            "$ref": "#/components/schemas/domain",
            "description": "The new domain row. Returned with `status: \"verified\"` and all four DNS checks passing — it can send and receive immediately (subject to the account email being verified)."
          },
          "dns": {
            "type": "array",
            "description": "Always empty for a claimed subdomain. Present so the shape matches createDomain, where it carries the records to publish.",
            "items": {
              "$ref": "#/components/schemas/dns-record"
            },
            "examples": [
              []
            ]
          }
        }
      },
      "contact-list": {
        "title": "Contact list",
        "description": "A named list of contacts (lst_…), used as a broadcast audience. GET /api/lists/:id adds member_count; other responses return the bare row.",
        "type": "object",
        "required": [
          "id",
          "user_id",
          "name",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "List id (lst_…).",
            "examples": [
              "lst_5Wd9JqRs"
            ]
          },
          "user_id": {
            "type": "string",
            "description": "Owning account (usr_…).",
            "examples": [
              "usr_7Fj3MnQw"
            ]
          },
          "name": {
            "type": "string",
            "description": "Display name, unique per account.",
            "examples": [
              "Newsletter"
            ]
          },
          "member_count": {
            "type": "integer",
            "description": "How many contacts the list holds. Detail (GET one) responses only.",
            "examples": [
              1284
            ]
          },
          "created_at": {
            "type": "integer",
            "description": "When the list was created, in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731200000
            ]
          },
          "updated_at": {
            "type": "integer",
            "description": "When the list last changed, in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731200000
            ]
          }
        }
      },
      "contact": {
        "title": "Contact",
        "description": "One address-book contact. Contacts accrue automatically from inbound/outbound mail and imports; role addresses (info@, support@) and consent state are tracked so broadcast audiences stay clean.",
        "type": "object",
        "required": [
          "id",
          "user_id",
          "email",
          "status",
          "source",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Contact id.",
            "examples": [
              "ctc_8Rt5NmZx"
            ]
          },
          "user_id": {
            "type": "string",
            "description": "Owning account (usr_…).",
            "examples": [
              "usr_7Fj3MnQw"
            ]
          },
          "email": {
            "type": "string",
            "description": "The address, trimmed and lowercased.",
            "examples": [
              "ada@example.com"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Display name, when known.",
            "examples": [
              "Ada Lovelace"
            ]
          },
          "first_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "First name, when known."
          },
          "last_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "Last name, when known."
          },
          "company": {
            "type": [
              "string",
              "null"
            ],
            "description": "Company, when known."
          },
          "website": {
            "type": [
              "string",
              "null"
            ],
            "description": "Website URL, when known."
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "description": "Phone number, when known."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "unsubscribed",
              "archived"
            ],
            "description": "Lifecycle state. Unsubscribed contacts are excluded from broadcast audiences."
          },
          "source": {
            "type": "string",
            "description": "Where the contact came from: 'inbound', 'outbound', 'manual', 'import', or a connected provider ('google', 'microsoft', 'apple', …).",
            "examples": [
              "inbound"
            ]
          },
          "role": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "1 for a role/non-personal address (info@, support@, …)."
          },
          "subscribed": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "1 = marketing opt-in (tracked separately from suppression)."
          },
          "properties": {
            "type": [
              "string",
              "null"
            ],
            "description": "Untyped custom fields as a JSON string, including { socials: {…} }."
          },
          "external_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your identifier for this contact, when supplied."
          },
          "first_seen_at": {
            "type": [
              "integer",
              "null"
            ],
            "description": "First time mail was exchanged with this address, in Unix epoch milliseconds (UTC); null when it has never happened.",
            "examples": [
              1769731200000
            ]
          },
          "last_seen_at": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Most recent time mail was exchanged with this address, in Unix epoch milliseconds (UTC); null when it has never happened.",
            "examples": [
              1769731200000
            ]
          },
          "msg_count": {
            "type": "integer",
            "description": "How many messages have been exchanged with this address.",
            "examples": [
              4
            ]
          },
          "created_at": {
            "type": "integer",
            "description": "When the contact was created, in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731200000
            ]
          },
          "updated_at": {
            "type": "integer",
            "description": "When the contact last changed, in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731200000
            ]
          }
        }
      },
      "create-app-password-request": {
        "title": "Create app password request body",
        "type": "object",
        "required": [
          "domain"
        ],
        "additionalProperties": false,
        "properties": {
          "domain": {
            "type": "string",
            "description": "The hosted domain this password covers. Must belong to the calling account.",
            "examples": [
              "acme.dev"
            ]
          },
          "domainId": {
            "type": "string",
            "description": "The domain by id (dom_…), as an alternative to `domain`.",
            "examples": [
              "dom_2VbXqTpN8rKw"
            ]
          },
          "address": {
            "type": "string",
            "description": "Local-part pattern within the domain — `*` (default, every address), `hello`, `support-*`, `*-agent`. No `@`: the domain is already named.",
            "examples": [
              "support-*"
            ]
          },
          "protocols": {
            "type": "array",
            "description": "What the password may authenticate. Defaults to `[\"imap\"]`.",
            "items": {
              "type": "string",
              "enum": [
                "imap",
                "api"
              ]
            },
            "examples": [
              [
                "imap",
                "api"
              ]
            ]
          },
          "label": {
            "type": "string",
            "description": "Optional name shown in the dashboard (e.g. the client or agent holding it).",
            "examples": [
              "support inbox agent"
            ]
          }
        }
      },
      "create-broadcast-request": {
        "title": "Create broadcast request body",
        "type": "object",
        "required": [
          "from"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string"
          },
          "from": {
            "type": "string"
          },
          "replyTo": {
            "type": "string"
          },
          "subject": {
            "type": "string"
          },
          "preview": {
            "type": "string"
          },
          "audience": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "all",
                  "list",
                  "filter"
                ]
              },
              "id": {
                "type": "string"
              },
              "query": {
                "type": "string"
              }
            }
          },
          "templateId": {
            "type": "string"
          },
          "html": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "footerAddress": {
            "type": "string"
          }
        }
      },
      "create-domain-request": {
        "title": "Create domain request body",
        "type": "object",
        "required": [
          "domain"
        ],
        "additionalProperties": false,
        "properties": {
          "domain": {
            "type": "string"
          }
        }
      },
      "create-domain-response": {
        "title": "Create domain response",
        "description": "POST /api/domains — the new domain plus the complete DNS record set to publish (every enabled provider's records merged, so one set works wherever the domain lands).",
        "type": "object",
        "required": [
          "domain",
          "dns"
        ],
        "properties": {
          "domain": {
            "$ref": "#/components/schemas/domain",
            "description": "The new domain row."
          },
          "dns": {
            "type": "array",
            "description": "The records to publish at your DNS provider.",
            "items": {
              "$ref": "#/components/schemas/dns-record"
            }
          },
          "warnings": {
            "type": "array",
            "description": "Present when a provider registration failed — the DNS set omits that provider's records until a later verify backfills them.",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "create-list-request": {
        "title": "Create list request body",
        "type": "object",
        "required": [
          "name"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string"
          }
        }
      },
      "create-route-request": {
        "title": "Create route request body",
        "type": "object",
        "required": [
          "match"
        ],
        "additionalProperties": false,
        "properties": {
          "match": {
            "type": "string",
            "description": "Address pattern: exact, *@domain, addr+*@domain, or /regex/."
          },
          "action": {
            "type": "string",
            "enum": [
              "webhook",
              "forward",
              "store",
              "drop",
              "agent"
            ],
            "description": "What to do with matching mail. Defaults to webhook."
          },
          "destination": {
            "type": "string",
            "description": "Required for action webhook (URL) or forward (address)."
          },
          "agentPrompt": {
            "type": "string",
            "description": "Required for action agent — instructions for the inbox agent."
          },
          "agentForwardTo": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "For action agent: addresses the inbox agent's forward tool may send to. The agent can always forward to your account email and any address on a verified domain you own; this adds extra approved addresses (e.g. an escalation inbox)."
          },
          "agentContext": {
            "type": "string",
            "enum": [
              "message",
              "thread",
              "sender"
            ],
            "description": "For action agent: how much mail the agent may read once the sender is verified (they replied to an earlier agent message from a DMARC-passing address). 'message' (default) = only the incoming email; 'thread' = this conversation; 'sender' = all past mail with this sender."
          },
          "forwardSpam": {
            "type": "boolean",
            "description": "For action forward: also re-send mail scored as spam. Defaults to false — a forward re-sends over our own outbound infrastructure, so spam-verdicted mail is held (event forward.spam_held) rather than relayed. Set true if you would rather receive spam than risk missing anything; either way the message is stored and still fires webhook/agent routes."
          }
        }
      },
      "create-scoped-key-request": {
        "title": "Create scoped key request body",
        "type": "object",
        "required": [
          "domainId"
        ],
        "additionalProperties": false,
        "properties": {
          "domainId": {
            "type": "string",
            "description": "The domain (dom_…) the new key is limited to. Must belong to the calling account.",
            "examples": [
              "dom_2VbXqTpN8rKw"
            ]
          },
          "name": {
            "type": "string",
            "description": "Optional label shown in the dashboard (e.g. which site/integration holds this key).",
            "examples": [
              "blog-wordpress"
            ]
          }
        }
      },
      "create-sequence-request": {
        "title": "Create sequence request body",
        "type": "object",
        "required": [
          "name",
          "steps"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "description": "Unique handle for this account; 1-64 chars of letters, digits, dot, dash, or underscore, starting alphanumeric.",
            "examples": [
              "dunning"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "active",
              "paused",
              "archived"
            ],
            "description": "Defaults to `draft`. A sequence enrolls nobody until it is `active`."
          },
          "from": {
            "type": "string",
            "description": "Default sender for every send step, on a verified domain. Required unless every send step carries its own `from`, or trigger.type is \"send\" (which inherits the triggering message's sender)."
          },
          "input": {
            "type": [
              "object",
              "null"
            ],
            "description": "The sequence's input signature: the shape every door must supply. A sequence is a function and this is how you declare its parameters — steps then read them as {{input.field}}, and a condition can branch on them. Omit it entirely for \"anything goes\"; you should not need a schema to write a two-step drip.",
            "additionalProperties": {
              "$ref": "#/components/schemas/input-field"
            },
            "examples": [
              {
                "invoiceId": {
                  "type": "string",
                  "required": true
                },
                "amountDue": {
                  "type": "number"
                }
              }
            ]
          },
          "triggers": {
            "type": "array",
            "description": "Doors into this sequence. A bare string is shorthand for { \"event\": \"<name>\" }. Replacing the set never bumps the sequence's version and never touches an enrollment in flight. A sequence with no triggers is enrolled only by naming it on a send or calling enroll — which is what \"manual\" used to mean.",
            "items": {
              "oneOf": [
                {
                  "type": "string"
                },
                {
                  "$ref": "#/components/schemas/create-trigger-request"
                }
              ]
            },
            "examples": [
              [
                "payment.failed",
                "invoice.overdue"
              ]
            ]
          },
          "steps": {
            "type": "array",
            "minItems": 1,
            "description": "At least one step, and at least one of them a `send` — a sequence that never sends is a no-op that still costs a row per contact.",
            "items": {
              "$ref": "#/components/schemas/sequence-step"
            }
          },
          "reentry": {
            "type": "string",
            "description": "`once` (default) refuses to enroll a contact already in flight. `always` restarts them: the in-flight enrollment is canceled and a new one begins. `cooldown:<duration>` (e.g. \"cooldown:30 days\") re-enrolls only when that long has passed since their last enrollment.",
            "examples": [
              "once",
              "always",
              "cooldown:30 days"
            ]
          },
          "exitOn": {
            "type": [
              "object",
              "null"
            ],
            "description": "Sequence-level exits, evaluated before EVERY step. `goal` completes the enrollment — they did the thing, stop chasing, which is the difference between a dunning sequence and harassment. `cancel` abandons it.",
            "properties": {
              "goal": {
                "$ref": "#/components/schemas/rule"
              },
              "cancel": {
                "$ref": "#/components/schemas/rule"
              }
            }
          }
        }
      },
      "create-template-request": {
        "title": "Create template request body",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "baseId": {
            "type": "string",
            "description": "Clone this base template (base_…) into your own. When set, name is optional (defaults to the base's name)."
          },
          "name": {
            "type": "string",
            "description": "Template name. Required unless baseId is given."
          },
          "subject": {
            "type": "string",
            "description": "Default subject line for sends."
          },
          "html": {
            "type": "string",
            "description": "Rendered, send-ready HTML."
          },
          "text": {
            "type": "string",
            "description": "Plaintext fallback."
          },
          "json": {
            "type": "string",
            "description": "Editor (TipTap) JSON source, for re-editing in the dashboard."
          },
          "theme": {
            "type": "string",
            "description": "Brand tokens JSON (bg, surface, primary, text, logo, …)."
          }
        }
      },
      "create-trigger-request": {
        "title": "Create trigger request body",
        "description": "Attach a door to a sequence. The sequence must have a `from` address: an event-triggered enrollment has no message to inherit a sender from. One trigger per (sequence, event) — a duplicate would enroll twice, and `reentry: \"once\"` would absorb the second silently.",
        "type": "object",
        "required": [
          "event"
        ],
        "additionalProperties": false,
        "properties": {
          "event": {
            "type": "string",
            "examples": [
              "payment.failed"
            ]
          },
          "filter": {
            "$ref": "#/components/schemas/rule"
          },
          "enabled": {
            "type": "boolean",
            "description": "Defaults to true."
          }
        }
      },
      "custom-event": {
        "title": "Custom event",
        "description": "A recorded application-level fact about one contact, as returned by GET /v1/events. Newest first — the surface for confirming a POST landed and for debugging a sequence that did not trigger.",
        "type": "object",
        "required": [
          "id",
          "name",
          "email",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Event id (cev_…).",
            "examples": [
              "cev_4b1c9e77"
            ]
          },
          "name": {
            "type": "string",
            "description": "Event name as posted.",
            "examples": [
              "trial.expiring"
            ]
          },
          "email": {
            "type": "string",
            "description": "The address the event is about, normalized to lowercase.",
            "examples": [
              "ada@example.com"
            ]
          },
          "contactId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The contact this resolved to, when the address is one we hold.",
            "examples": [
              "ctc_8Rt5NmZx"
            ]
          },
          "payload": {
            "type": [
              "object",
              "null"
            ],
            "description": "The context posted with the event."
          },
          "dedupeKey": {
            "type": [
              "string",
              "null"
            ],
            "description": "The idempotency key posted with the event."
          },
          "createdAt": {
            "type": "number",
            "description": "When the event was recorded, in ms epoch.",
            "examples": [
              1799999999000
            ]
          }
        }
      },
      "decrypt-request": {
        "title": "Decrypt request",
        "description": "Inputs to decrypt — a local hybrid-envelope decryption, no API call.",
        "type": "object",
        "required": [
          "envelope",
          "privateKey"
        ],
        "additionalProperties": false,
        "properties": {
          "envelope": {
            "type": "string",
            "description": "The at-rest envelope JSON string ({v,keyAlg,fp,enc,iv,wrappedKey,ciphertext}) from MailKite."
          },
          "privateKey": {
            "type": "string",
            "description": "Your RSA private key in PKCS8/PEM form (-----BEGIN PRIVATE KEY-----) — the match to the public key the envelope was wrapped to."
          }
        }
      },
      "delete-domain-response": {
        "title": "Delete domain response",
        "description": "DELETE /api/domains/:id — confirmation plus what infrastructure teardown accomplished. Teardown is best-effort and never blocks the delete; warnings record anything left behind.",
        "type": "object",
        "required": [
          "ok",
          "cleanup"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "cleanup": {
            "type": "object",
            "required": [
              "deregistered",
              "dnsDeleted",
              "warnings"
            ],
            "description": "What the teardown did.",
            "properties": {
              "deregistered": {
                "type": "array",
                "description": "Provider types the domain was deregistered from.",
                "items": {
                  "type": "string"
                }
              },
              "dnsDeleted": {
                "type": "integer",
                "description": "How many MailKite-hosted DNS records were removed (0 when we don't host the domain's DNS)."
              },
              "warnings": {
                "type": "array",
                "description": "Steps that failed — the only record that something was left behind.",
                "items": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "delete-message-response": {
        "title": "DeleteMessageResponse",
        "type": "object",
        "required": [
          "id",
          "deleted",
          "deleted_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "deleted": {
            "type": "boolean",
            "const": true
          },
          "deleted_at": {
            "type": "integer",
            "description": "Original soft-deletion timestamp, epoch milliseconds."
          }
        },
        "additionalProperties": false
      },
      "delete-sequence-response": {
        "title": "Delete sequence response body",
        "type": "object",
        "required": [
          "id",
          "deleted"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "seq_3c8f21aa"
            ]
          },
          "deleted": {
            "type": "boolean"
          },
          "enrollmentsCanceled": {
            "type": "number",
            "description": "How many in-flight contacts were retired by the delete."
          }
        }
      },
      "delete-trigger-response": {
        "title": "Delete trigger response body",
        "type": "object",
        "required": [
          "id",
          "deleted"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "deleted": {
            "type": "boolean"
          }
        }
      },
      "deliver-to-route-request": {
        "title": "Deliver to route request",
        "description": "Which stored messages to POST to this route. Unlike a retry, these messages need no existing delivery on the route — this is how mail that arrived BEFORE the route existed reaches it. At most 50 ids per request.",
        "type": "object",
        "required": [
          "messageIds"
        ],
        "properties": {
          "messageIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Stored messages (msg_…) to deliver to this route's webhook, in any order.",
            "examples": [
              [
                "msg_2Hk9QpVn4tLd",
                "msg_5Tq1RvXz"
              ]
            ]
          }
        }
      },
      "delivery-attempt": {
        "title": "Delivery attempt",
        "description": "One try at POSTing a webhook — the request we sent and the response we got back. This is the record the request/response inspector reads; rows are pruned after 45 days.",
        "type": "object",
        "required": [
          "id",
          "delivery_id",
          "message_id",
          "attempt_number",
          "ok",
          "requested_at",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Attempt id (att_…).",
            "examples": [
              "att_3Yh7GbFd"
            ]
          },
          "delivery_id": {
            "type": "string",
            "description": "The delivery this attempt belongs to (dlv_…).",
            "examples": [
              "dlv_6Kp2LsWq"
            ]
          },
          "message_id": {
            "type": "string",
            "description": "The message delivered (msg_…).",
            "examples": [
              "msg_2Hk9QpVn4tLd"
            ]
          },
          "attempt_number": {
            "type": "integer",
            "description": "1-based attempt counter within the delivery.",
            "examples": [
              1
            ]
          },
          "ok": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "1 when the attempt succeeded."
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Failure classification when not ok (e.g. 'http_5xx', 'timeout', 'no-ack')."
          },
          "request_headers": {
            "type": [
              "string",
              "null"
            ],
            "description": "Request headers we sent, as a JSON string (content-type, x-mailkite-signature, x-mailkite-timestamp). Null for attempts recorded before capture existed."
          },
          "request_body": {
            "type": [
              "string",
              "null"
            ],
            "description": "The JSON payload we POSTed, truncated to ~4KB. Null for attempts recorded before capture existed."
          },
          "response_status": {
            "type": [
              "integer",
              "null"
            ],
            "description": "HTTP status the endpoint returned.",
            "examples": [
              200
            ]
          },
          "response_headers": {
            "type": [
              "string",
              "null"
            ],
            "description": "Response headers as a JSON string."
          },
          "response_body": {
            "type": [
              "string",
              "null"
            ],
            "description": "Response body, truncated to ~4KB."
          },
          "duration_ms": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Round-trip time of the attempt.",
            "examples": [
              142
            ]
          },
          "requested_at": {
            "type": "integer",
            "description": "When the attempt started, in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731200000
            ]
          },
          "created_at": {
            "type": "integer",
            "description": "When the audit row was recorded, in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731200000
            ]
          }
        }
      },
      "delivery-attempts-response": {
        "title": "Delivery attempts response",
        "description": "Every captured attempt for one delivery, newest first — the request we sent and the response we got back. Captures are retained for 45 days.",
        "type": "object",
        "required": [
          "attempts"
        ],
        "properties": {
          "attempts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/delivery-attempt"
            },
            "description": "Attempts for this delivery, newest first."
          }
        }
      },
      "delivery": {
        "title": "Delivery",
        "description": "One webhook delivery of a stored message — every retry and replay is a new delivery row, so the history is append-only.",
        "type": "object",
        "required": [
          "id",
          "message_id",
          "route_id",
          "url",
          "status",
          "attempts",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Delivery id (dlv_…).",
            "examples": [
              "dlv_6Kp2LsWq"
            ]
          },
          "message_id": {
            "type": "string",
            "description": "The message this delivery carries (msg_…).",
            "examples": [
              "msg_2Hk9QpVn4tLd"
            ]
          },
          "route_id": {
            "type": "string",
            "description": "The route whose webhook was targeted (rte_…).",
            "examples": [
              "rte_4Bn8XcVm"
            ]
          },
          "url": {
            "type": "string",
            "description": "The webhook URL the body was POSTed to.",
            "examples": [
              "https://myapp.ai/hooks/mailkite"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "delivered",
              "failed",
              "paused"
            ],
            "description": "Current state of this delivery."
          },
          "attempts": {
            "type": "integer",
            "description": "How many attempts this delivery has made.",
            "examples": [
              1
            ]
          },
          "last_status_code": {
            "type": [
              "integer",
              "null"
            ],
            "description": "HTTP status of the most recent attempt.",
            "examples": [
              200
            ]
          },
          "next_retry_at": {
            "type": [
              "integer",
              "null"
            ],
            "description": "When the auto-retry cron will re-attempt a failed delivery; null when nothing is due, in Unix epoch milliseconds (UTC); null when it has never happened.",
            "examples": [
              1769731200000
            ]
          },
          "retry_attempts": {
            "type": "integer",
            "description": "How many automatic retries have been scheduled so far."
          },
          "created_at": {
            "type": "integer",
            "description": "When the delivery was created, in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731200000
            ]
          },
          "updated_at": {
            "type": "integer",
            "description": "When the delivery last changed, in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731200000
            ]
          }
        }
      },
      "dns-record": {
        "title": "DNS record",
        "description": "One DNS record to publish for a domain, exactly as your DNS provider expects it.",
        "type": "object",
        "required": [
          "type",
          "name",
          "value"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "MX",
              "TXT",
              "CNAME",
              "A"
            ],
            "description": "Record type."
          },
          "name": {
            "type": "string",
            "description": "Full hostname (FQDN) the record lives at.",
            "examples": [
              "myapp.ai"
            ]
          },
          "value": {
            "type": "string",
            "description": "Bare RDATA value.",
            "examples": [
              "v=spf1 include:spf.mailkite.dev ~all"
            ]
          },
          "priority": {
            "type": "integer",
            "description": "MX priority — present on MX records only.",
            "examples": [
              10
            ]
          }
        }
      },
      "domain-availability-response": {
        "title": "Domain availability response",
        "description": "GET /api/domains/register/check — whether a domain can be registered through MailKite, and at what price. Read-only; no charge.",
        "type": "object",
        "required": [
          "configured",
          "domain",
          "available",
          "premium"
        ],
        "properties": {
          "configured": {
            "type": "boolean",
            "description": "False when domain registration isn't available on this deployment — the other fields are then placeholders."
          },
          "domain": {
            "type": "string",
            "description": "The normalized domain that was checked.",
            "examples": [
              "myapp.ai"
            ]
          },
          "available": {
            "type": "boolean",
            "description": "True when the domain can be registered."
          },
          "premium": {
            "type": "boolean",
            "description": "True for registry-premium names, which can't be registered here."
          },
          "reason": {
            "type": "string",
            "description": "Why an unavailable domain can't be registered (e.g. 'Already on MailKite')."
          },
          "price": {
            "type": "object",
            "required": [
              "amount",
              "currency",
              "period",
              "periodUnit"
            ],
            "description": "What you'd actually be charged (registrar cost grossed up with fees). Absent when unavailable.",
            "properties": {
              "amount": {
                "type": "number",
                "description": "Price in major units of `currency`.",
                "examples": [
                  12.99
                ]
              },
              "currency": {
                "type": "string",
                "description": "ISO 4217 currency code.",
                "examples": [
                  "USD"
                ]
              },
              "period": {
                "type": "integer",
                "description": "How many periodUnits the amount covers.",
                "examples": [
                  1
                ]
              },
              "periodUnit": {
                "type": "string",
                "enum": [
                  "y",
                  "m"
                ],
                "description": "Unit of `period` — years or months."
              }
            }
          }
        }
      },
      "domain-response": {
        "title": "Domain envelope",
        "description": "The uniform write-response for domain settings (retention, encryption): the updated domain row.",
        "type": "object",
        "required": [
          "domain"
        ],
        "properties": {
          "domain": {
            "$ref": "#/components/schemas/domain",
            "description": "The updated domain row (without webhook enrichment)."
          }
        }
      },
      "domain": {
        "title": "Domain",
        "description": "A sending/receiving domain on your account. List and detail responses enrich the stored row with the domain's catch-all webhook (webhookUrl/webhookAckMode/webhookStatus) and the account's daily-limit state; write responses return the row alone, so those fields are optional here.",
        "type": "object",
        "required": [
          "id",
          "user_id",
          "domain",
          "status",
          "mx_verified",
          "spf_verified",
          "dkim_verified",
          "dmarc_verified",
          "provider",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Domain id (dom_…).",
            "examples": [
              "dom_2VbXqTpN8rKw"
            ]
          },
          "user_id": {
            "type": "string",
            "description": "Owning account (usr_…).",
            "examples": [
              "usr_7Fj3MnQw"
            ]
          },
          "domain": {
            "type": "string",
            "description": "The domain name, lowercased.",
            "examples": [
              "myapp.ai"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "pending",
              "verified",
              "failed"
            ],
            "description": "Overall verification state. Inbound routing requires at least MX verification; `verified` follows the MX check."
          },
          "mx_verified": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "1 once the MX record resolves to MailKite — the gate for inbound mail, webhooks, and routes."
          },
          "spf_verified": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "1 once the SPF TXT record is in place."
          },
          "dkim_verified": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "1 once the DKIM records are accepted by the sending provider."
          },
          "dmarc_verified": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "1 once a DMARC policy is published."
          },
          "zero_retention": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "1 when zero-retention passthrough is on: inbound mail is delivered to the webhook and never stored (no replay, no dashboard history)."
          },
          "track_opens": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "Domain-wide default for outbound open tracking (a per-send trackOpens overrides it)."
          },
          "enc_public_key": {
            "type": [
              "string",
              "null"
            ],
            "description": "At-rest encryption public key (PEM SPKI, armored OpenPGP key, or X.509 cert). null = encryption off."
          },
          "enc_key_alg": {
            "type": [
              "string",
              "null"
            ],
            "description": "Key algorithm recorded for the uploaded key ('RSA-OAEP-256', 'PGP', or 'S/MIME'). null = encryption off."
          },
          "enc_fingerprint": {
            "type": [
              "string",
              "null"
            ],
            "description": "Fingerprint of the uploaded key (sha256 of the SPKI/cert DER, or the OpenPGP fingerprint)."
          },
          "enc_format": {
            "type": [
              "string",
              "null"
            ],
            "description": "Which client-decryptable standard the key is: 'pgp' or 'smime'. null = off or legacy custom envelope."
          },
          "provider": {
            "type": "string",
            "description": "Email sending provider type currently backing this domain (e.g. 'ses', 'cloudflare'). Assigned by MailKite, never chosen by the caller.",
            "examples": [
              "ses"
            ]
          },
          "email_provider_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Registry id of the concrete provider instance backing the domain."
          },
          "dns_tokens": {
            "type": [
              "string",
              "null"
            ],
            "description": "Provider-specific DNS tokens as a JSON string (e.g. SES DKIM CNAME targets). Feeds the dns record set; null until providers report them."
          },
          "last_checked_at": {
            "type": [
              "integer",
              "null"
            ],
            "description": "When domain verification last ran, in Unix epoch milliseconds (UTC); null when it has never happened.",
            "examples": [
              1769731200000
            ]
          },
          "created_at": {
            "type": "integer",
            "description": "When the domain was added, in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731200000
            ]
          },
          "webhookUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "URL of the domain's catch-all webhook, or null when none is set. List/detail responses only.",
            "examples": [
              "https://myapp.ai/hooks/mailkite"
            ]
          },
          "webhookAckMode": {
            "type": [
              "string",
              "null"
            ],
            "description": "The webhook's acknowledgement mode: 'lenient' (any 2xx) or 'ack' (2xx + explicit ok). List/detail responses only."
          },
          "webhookStatus": {
            "type": [
              "object",
              "null"
            ],
            "description": "Health of the webhook's most recent delivery, or null when it has never been exercised. List/detail responses only.",
            "properties": {
              "status": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "'ok', 'fail', 'running', or 'timeout'."
              },
              "message": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Human-readable detail of the last outcome (e.g. 'HTTP 500')."
              },
              "code": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "HTTP status of the last delivery, when relevant."
              },
              "at": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "When the last outcome landed, in Unix epoch milliseconds (UTC); null when it has never happened.",
                "examples": [
                  1769731200000
                ]
              },
              "consecutiveFailures": {
                "type": "integer",
                "description": "Current failing streak (0 when healthy)."
              },
              "firstFailedAt": {
                "type": [
                  "integer",
                  "null"
                ],
                "description": "When the current failing streak began, in Unix epoch milliseconds (UTC); null when it has never happened.",
                "examples": [
                  1769731200000
                ]
              },
              "unhealthy": {
                "type": "boolean",
                "description": "True once the failing streak passes the unhealthy threshold."
              }
            }
          },
          "dailyLimitReached": {
            "type": "boolean",
            "description": "True when the owning account has hit its daily send cap. List/detail responses only."
          }
        }
      },
      "email-received-event": {
        "title": "email.received webhook event",
        "description": "The JSON body MailKite POSTs to your webhook when mail arrives at an address on a verified domain. The same body is re-sent verbatim on an automatic retry or a manual replay, so every field describes the original message — never the moment of the POST. Verify the x-mailkite-signature header over the raw body before trusting it.\n\nEVERY FIELD BELOW IS ALWAYS PRESENT. A value we do not have is null; nothing is ever omitted, at any nesting level. Write one rule for absence and it will hold for the whole payload. null means \"we do not know\" and is never a stand-in for a value we verified — in particular a null auth verdict is NOT a pass. See docs/api/models/email-received-event.md.",
        "type": "object",
        "required": [
          "id",
          "type",
          "from",
          "to",
          "subject",
          "text",
          "html",
          "textFromHtml",
          "threadId",
          "receivedAt",
          "receivedAtIso",
          "auth",
          "attachments"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "description": "Stable message id: the literal prefix `msg_` followed by a version-4 UUID with its hyphens removed (32 lowercase hex characters, 36 in total). Identical across retries and replays — use it as your idempotency key. To store it as a native UUID, drop the `msg_` prefix and re-insert hyphens at 8-4-4-4-12; the transform is lossless in both directions. Every MailKite id is built this way and differs only in the prefix (`rte_`, `dom_`, `usr_`, …).",
            "pattern": "^msg_[0-9a-f]{32}$",
            "minLength": 36,
            "maxLength": 36,
            "examples": [
              "msg_4f3c1a9e2b7d48e1a05c6f8b3d2e7a91"
            ]
          },
          "type": {
            "type": "string",
            "const": "email.received",
            "description": "The event type. Always email.received for inbound mail."
          },
          "from": {
            "type": "object",
            "description": "The envelope sender.",
            "required": [
              "address",
              "name"
            ],
            "properties": {
              "address": {
                "type": "string",
                "description": "Sender email address, taken from the SMTP envelope (MAIL FROM) — the address we actually routed on.",
                "examples": [
                  "ada@example.com"
                ]
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Sender display name, decoded from the MIME From: header. null when the message carried no display name, or when the From: header names a different address than the envelope did — as happens with mailing lists, forwarders, and spoofed mail. It is sender-asserted and unverified, like the Date: header: never use it for authorization, and check the auth block before showing it as an identity.",
                "examples": [
                  "Ada Lovelace"
                ]
              }
            },
            "additionalProperties": false
          },
          "to": {
            "type": "array",
            "description": "The recipient address this delivery is for. One entry per event: a message addressed to several of your addresses is routed and delivered once per matching address.",
            "minItems": 1,
            "items": {
              "type": "object",
              "required": [
                "address",
                "name"
              ],
              "properties": {
                "address": {
                  "type": "string",
                  "description": "Recipient email address, taken from the SMTP envelope (RCPT TO).",
                  "examples": [
                    "support@myapp.ai"
                  ]
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Recipient display name, decoded from the MIME To: header. null when absent, or when the To: header does not name this exact address (e.g. mail delivered via Bcc or an alias).",
                  "examples": [
                    "Support"
                  ]
                }
              },
              "additionalProperties": false
            }
          },
          "subject": {
            "type": [
              "string",
              "null"
            ],
            "description": "Decoded subject line. null when the message had no Subject header."
          },
          "text": {
            "type": [
              "string",
              "null"
            ],
            "description": "Plain-text body, already MIME-decoded. null when the message had no text/plain part."
          },
          "html": {
            "type": [
              "string",
              "null"
            ],
            "description": "HTML body, already MIME-decoded. null when the message had no text/html part."
          },
          "textFromHtml": {
            "type": [
              "string",
              "null"
            ],
            "description": "Plain text WE DERIVED from `html` — not something the sender wrote. Populated only when `text` is null and `html` is not, so it never duplicates a body already present and the consumer's rule is one line: `text ?? textFromHtml`. null whenever `text` is present, or when there was no `html` to convert. About 20% of real inbound mail carries no text/plain part; this is that gap filled without putting words in the sender's mouth — `text` and `html` remain exactly what arrived.",
            "examples": [
              "Looks good — approved!"
            ]
          },
          "threadId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The conversation root: the message's In-Reply-To/References root, falling back to its own Message-ID. Pass it back as send()'s inReplyTo to reply in-thread. null when the message carried no usable id."
          },
          "receivedAt": {
            "type": "integer",
            "description": "When MailKite accepted the message, in Unix epoch milliseconds (UTC). This is arrival time, not delivery time — it is read from the stored message, so a retry hours later or a replay months later reports the same instant. Distinct from the sender-supplied Date: header, which is self-asserted and spoofable.",
            "examples": [
              1785196800000
            ]
          },
          "receivedAtIso": {
            "type": "string",
            "format": "date-time",
            "description": "The same instant as receivedAt, rendered as an RFC 3339 / ISO 8601 UTC timestamp.",
            "examples": [
              "2026-07-28T00:00:00.000Z"
            ]
          },
          "auth": {
            "type": "object",
            "description": "Authentication and spam verdicts. SPF and DKIM are computed at our receiving edge, where the connecting IP and SMTP envelope are still visible; DMARC is derived from them in the Worker (see docs/api/lib/dmarc.md). Every field is ALWAYS PRESENT and is null when that check did not run or produced no verdict — treat null as 'unknown', NEVER as 'pass'. SPF/DKIM values are passed through from the edge rather than re-mapped, so match the ones you care about and treat anything else as unknown. The spam verdict is INFORMATIONAL: we deliver flagged mail to you exactly like any other message and never drop, defer, or withhold a webhook because of it — you have context we don't, so you set the threshold. Use spamSignals to decide what to act on.",
            "required": [
              "spf",
              "dkim",
              "dmarc",
              "spam",
              "spamScore",
              "spamSignals"
            ],
            "additionalProperties": false,
            "properties": {
              "spf": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "SPF result for the sending IP. Typically pass, fail, softfail, or neutral.",
                "examples": [
                  "pass"
                ]
              },
              "dkim": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "DKIM signature result. Typically pass or fail.",
                "examples": [
                  "pass"
                ]
              },
              "dmarc": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "DMARC evaluation result for the message, derived in the Worker from the SPF and DKIM results plus identifier alignment against the From: header domain (RFC 7489 §4.2). One of: 'pass' (SPF or DKIM passed AND its domain aligned), 'fail' (the From domain publishes a DMARC record and neither identifier aligned and passed), 'none' (the From domain publishes no DMARC record — nothing was asserted, so nothing failed), 'temperror' (the policy lookup itself failed and the answer is unknown). null when evaluation could not run at all. 'none' and 'fail' are NOT the same thing: only 'fail' means a published policy was violated.",
                "examples": [
                  "pass"
                ]
              },
              "spam": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Spam verdict: 'spam', 'ham', or null when the message was not scored. NEVER affects delivery — this message reached you because we do not act on this field. Edges running rspamd may pass through its action name instead.",
                "examples": [
                  "ham"
                ]
              },
              "spamScore": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "How strongly the message scored as spam, from 0 (clean) to 1. null when not scored. The threshold behind the `spam` verdict is 0.5, but you are free to pick your own — that is the point of publishing the number.",
                "minimum": 0,
                "maximum": 1,
                "examples": [
                  0.85
                ]
              },
              "spamSignals": {
                "type": [
                  "array",
                  "null"
                ],
                "description": "Which signals fired, and therefore WHY the score is what it is. Stable identifiers you can branch on: dnsbl_listed (the connecting IP is on a blocklist), dbl_listed (the From domain is on a domain blocklist), spf_fail, spf_softfail, dkim_fail, dmarc_fail, unaligned_from (the visible From: domain differs from the envelope and nothing authenticated either), no_auth (no SPF, DKIM or DMARC at all). Empty when nothing fired; null when the message was not scored.",
                "items": {
                  "type": "string"
                },
                "examples": [
                  [
                    "dnsbl_listed",
                    "spf_fail"
                  ],
                  [
                    "dbl_listed"
                  ]
                ]
              }
            }
          },
          "attachments": {
            "type": "array",
            "description": "Decoded attachments. Empty array when the message had none. Every key below is always present on every entry. `url` and `content` are the one mutually exclusive pair — exactly one of them is non-null: normally `url` (a signed, credential-free GET link valid for 7 days), and `content` (base64 bytes inlined) on zero-retention and at-rest-encrypted domains where no object is stored. Handle both.",
            "items": {
              "type": "object",
              "required": [
                "id",
                "filename",
                "contentType",
                "size",
                "contentId",
                "disposition",
                "url",
                "content"
              ],
              "additionalProperties": false,
              "oneOf": [
                {
                  "properties": {
                    "url": {
                      "type": "string"
                    },
                    "content": {
                      "type": "null"
                    }
                  }
                },
                {
                  "properties": {
                    "url": {
                      "type": "null"
                    },
                    "content": {
                      "type": "string"
                    }
                  }
                }
              ],
              "properties": {
                "id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Attachment id, `<messageId>:<index>`. null when the bytes are inlined in `content` (nothing was stored to reference).",
                  "examples": [
                    "msg_4f3c1a9e2b7d48e1a05c6f8b3d2e7a91:0"
                  ]
                },
                "filename": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Filename as sent, decoded. null when the part had no name.",
                  "examples": [
                    "po.pdf"
                  ]
                },
                "contentType": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "MIME type as declared by the sender. null when absent.",
                  "examples": [
                    "application/pdf"
                  ]
                },
                "size": {
                  "type": "integer",
                  "description": "Decoded size in bytes.",
                  "examples": [
                    18213
                  ]
                },
                "contentId": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The part's Content-ID, unbracketed. Pair it with `disposition: \"inline\"` to map an inline image back to the HTML body's `src=\"cid:<contentId>\"`. null when the part declared none.",
                  "examples": [
                    "logo@example.com"
                  ]
                },
                "disposition": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The part's Content-Disposition: 'attachment' or 'inline'. null when the sender declared none.",
                  "examples": [
                    "attachment"
                  ]
                },
                "url": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Signed, time-limited GET link — fetch it with no credentials. Valid for 7 days, after which the object is deleted (410 Gone). null when `content` carries the bytes instead.",
                  "examples": [
                    "https://api.mailkite.dev/att/4f3c1a9e2b7d48e1a05c6f8b3d2e7a91/0?exp=1754265600&sig=…"
                  ]
                },
                "content": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "The file bytes, base64-encoded, inlined because nothing was stored. Non-null only on zero-retention or at-rest-encrypted domains, in place of `url`.",
                  "contentEncoding": "base64"
                }
              }
            }
          }
        }
      },
      "encrypt-request": {
        "title": "Encrypt request",
        "description": "Inputs to encrypt — a local hybrid-envelope encryption, no API call.",
        "type": "object",
        "required": [
          "plaintext",
          "publicKey"
        ],
        "additionalProperties": false,
        "properties": {
          "plaintext": {
            "type": "string",
            "description": "The UTF-8 string to encrypt (e.g. a message body you're about to store)."
          },
          "publicKey": {
            "type": "string",
            "description": "The recipient domain's RSA public key in SPKI/PEM form (-----BEGIN PUBLIC KEY-----), ≥2048-bit."
          }
        }
      },
      "enrollment-response": {
        "title": "Enrollment response body",
        "type": "object",
        "required": [
          "enrollment"
        ],
        "properties": {
          "enrollment": {
            "$ref": "#/components/schemas/enrollment"
          }
        }
      },
      "enrollment": {
        "title": "Sequence enrollment",
        "description": "One contact walking one sequence — where they are, and what happens next. `active` means a step is due at `nextAt`; `waiting` means parked on an event; `completed`, `canceled`, and `failed` are terminal.",
        "type": "object",
        "required": [
          "id",
          "sequenceId",
          "email",
          "status",
          "stepIndex",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Enrollment id (enr_…).",
            "examples": [
              "enr_5b2d9f01"
            ]
          },
          "sequenceId": {
            "type": "string",
            "examples": [
              "seq_3c8f21aa"
            ]
          },
          "sequenceVersion": {
            "type": "number",
            "description": "The sequence version this contact is walking, pinned at enrollment."
          },
          "email": {
            "type": "string",
            "description": "The enrolled address, normalized to lowercase."
          },
          "contactId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The contact this resolved to, when the address is one you hold."
          },
          "from": {
            "type": "string",
            "description": "The sender frozen at enrollment — send steps use it unless they override with their own `from`."
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "waiting",
              "completed",
              "canceled",
              "failed"
            ]
          },
          "stepIndex": {
            "type": "number",
            "description": "The next step to run, indexed into the compiled program."
          },
          "nextAt": {
            "type": [
              "number",
              "null"
            ],
            "description": "When the next step is due, in ms epoch. Null while parked on an event or terminal."
          },
          "waitEvent": {
            "type": [
              "string",
              "null"
            ],
            "description": "The event name this enrollment is parked on."
          },
          "waitUntil": {
            "type": [
              "number",
              "null"
            ],
            "description": "When the parked wait times out, in ms epoch."
          },
          "input": {
            "type": [
              "object",
              "null"
            ],
            "description": "The validated input this contact was enrolled with. Steps read it as {{input.*}} — and {{event.*}}, the original alias."
          },
          "trigger": {
            "type": [
              "object",
              "null"
            ],
            "description": "How the enrollment started: { type: 'event'|'send'|'manual', event?, messageId?, from?, to?, subject?, templateId? }. Steps read it as {{trigger.*}}. `input` is what you passed; this is how it started."
          },
          "cancelKey": {
            "type": [
              "string",
              "null"
            ],
            "description": "Your own key for this enrollment — cancel by it without needing our id."
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why a failed enrollment stopped."
          },
          "createdAt": {
            "type": "number"
          },
          "updatedAt": {
            "type": "number"
          }
        }
      },
      "event-name": {
        "title": "Event name",
        "description": "One event name this account works with — either observed (posted to /v1/events at least once) or declared (referenced by a sequence's trigger or a wait_for_event step, possibly never sent yet). A name can be both.",
        "type": "object",
        "required": [
          "name",
          "count",
          "usedBy"
        ],
        "properties": {
          "name": {
            "type": "string",
            "examples": [
              "invoice.paid"
            ]
          },
          "count": {
            "type": "number",
            "description": "How many of these have been recorded. 0 for a name only a sequence declares."
          },
          "lastSeenAt": {
            "type": [
              "number",
              "null"
            ],
            "description": "When one last arrived, in ms epoch. Null if never."
          },
          "usedBy": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Names of the sequences that trigger on or wait for this event."
          }
        }
      },
      "get-domain-response": {
        "title": "Domain detail response",
        "description": "GET /api/domains/:id — the domain (with webhook enrichment) plus its current DNS record set.",
        "type": "object",
        "required": [
          "domain",
          "dns"
        ],
        "properties": {
          "domain": {
            "$ref": "#/components/schemas/domain"
          },
          "dns": {
            "type": "array",
            "description": "The records to publish at your DNS provider.",
            "items": {
              "$ref": "#/components/schemas/dns-record"
            }
          }
        }
      },
      "google-auth-request": {
        "title": "Google sign-in request body",
        "type": "object",
        "required": [
          "code",
          "redirectUri"
        ],
        "additionalProperties": false,
        "properties": {
          "code": {
            "type": "string",
            "description": "One-time authorization code from Google's redirect."
          },
          "redirectUri": {
            "type": "string",
            "description": "The redirect URI the code was issued to — must match a registered URI.",
            "examples": [
              "https://mailkite.dev/auth/google/callback"
            ]
          },
          "ref": {
            "type": "string",
            "description": "Referral code, when any (Google sign-in doubles as signup)."
          }
        }
      },
      "input-field": {
        "title": "Sequence input field",
        "description": "One field of a sequence's input signature. Scalars only — these are exactly the types a merge tag or a condition can read, so nothing can be declared that a step cannot then use.",
        "type": "object",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "string",
              "number",
              "boolean"
            ]
          },
          "required": {
            "type": "boolean",
            "description": "Enrollment is refused when this field is missing — so you find out the sequence cannot run, instead of discovering it later as a blank merge tag in a real email."
          },
          "default": {
            "type": [
              "string",
              "number",
              "boolean"
            ],
            "description": "Used when the field is absent. Cannot be combined with `required`, which would make it unreachable."
          },
          "description": {
            "type": "string",
            "description": "What this field is for. Shown in the editor."
          }
        }
      },
      "list-enrollments-response": {
        "title": "List enrollments response body",
        "type": "object",
        "required": [
          "enrollments"
        ],
        "properties": {
          "enrollments": {
            "type": "array",
            "description": "Enrollments on this sequence, newest first.",
            "items": {
              "$ref": "#/components/schemas/enrollment"
            }
          }
        }
      },
      "list-event-names-response": {
        "title": "List event names response body",
        "type": "object",
        "required": [
          "events"
        ],
        "properties": {
          "events": {
            "type": "array",
            "description": "Most-recently-seen first, then names only a sequence declares (alphabetical — they have no recency to sort by).",
            "items": {
              "$ref": "#/components/schemas/event-name"
            }
          }
        }
      },
      "list-events-response": {
        "title": "List events response body",
        "type": "object",
        "required": [
          "events"
        ],
        "properties": {
          "events": {
            "type": "array",
            "description": "Recorded events, newest first.",
            "items": {
              "$ref": "#/components/schemas/custom-event"
            }
          }
        }
      },
      "list-runs-response": {
        "title": "Enrollment runs response body",
        "type": "object",
        "required": [
          "enrollment",
          "runs"
        ],
        "properties": {
          "enrollment": {
            "$ref": "#/components/schemas/enrollment"
          },
          "runs": {
            "type": "array",
            "description": "Executed steps in order.",
            "items": {
              "$ref": "#/components/schemas/step-run"
            }
          }
        }
      },
      "list-schemas-response": {
        "title": "List schemas response",
        "description": "GET /v1/schemas — every published JSON Schema with the URL it's served from.",
        "type": "object",
        "required": [
          "version",
          "schemas"
        ],
        "properties": {
          "version": {
            "type": "string",
            "description": "The contract version these schemas belong to.",
            "examples": [
              "0.16.0"
            ]
          },
          "schemas": {
            "type": "array",
            "description": "All published schemas, sorted by name.",
            "items": {
              "type": "object",
              "required": [
                "name",
                "title",
                "url"
              ],
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Schema id — the filename stem api.json references.",
                  "examples": [
                    "send-request"
                  ]
                },
                "title": {
                  "type": "string",
                  "description": "Human title from the schema.",
                  "examples": [
                    "Send request body"
                  ]
                },
                "description": {
                  "type": "string",
                  "description": "The schema's description, when it has one."
                },
                "url": {
                  "type": "string",
                  "description": "Absolute URL the schema is served from.",
                  "examples": [
                    "https://api.mailkite.dev/v1/schemas/send-request.json"
                  ]
                }
              }
            }
          }
        }
      },
      "list-sequences-response": {
        "title": "List sequences response body",
        "type": "object",
        "required": [
          "sequences"
        ],
        "properties": {
          "sequences": {
            "type": "array",
            "description": "Your sequences, newest first, each with live enrollment counts. Archived ones are omitted.",
            "items": {
              "$ref": "#/components/schemas/sequence"
            }
          }
        }
      },
      "list-triggers-response": {
        "title": "List triggers response body",
        "type": "object",
        "required": [
          "triggers"
        ],
        "properties": {
          "triggers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/sequence-trigger"
            }
          }
        }
      },
      "login-request": {
        "title": "Login request body",
        "type": "object",
        "required": [
          "email",
          "password"
        ],
        "additionalProperties": false,
        "properties": {
          "email": {
            "type": "string",
            "description": "Account email.",
            "examples": [
              "ada@example.com"
            ]
          },
          "password": {
            "type": "string",
            "description": "Account password."
          }
        }
      },
      "mailbox-messages-response": {
        "title": "Mailbox message page",
        "description": "One page of a mailbox's messages, newest first. Read with an app password that grants `api`.",
        "type": "object",
        "required": [
          "messages",
          "nextBefore"
        ],
        "additionalProperties": false,
        "properties": {
          "messages": {
            "type": "array",
            "description": "The page, newest first.",
            "items": {
              "type": "object",
              "required": [
                "uid",
                "flags",
                "internaldate",
                "from_addr",
                "to_addr",
                "subject",
                "size"
              ],
              "additionalProperties": false,
              "properties": {
                "uid": {
                  "type": "number",
                  "description": "Stable per-mailbox id — also the IMAP UID, so both protocols name a message the same way.",
                  "examples": [
                    42
                  ]
                },
                "flags": {
                  "type": "string",
                  "description": "Space-separated IMAP flags WITHOUT the leading backslash, e.g. `Seen Flagged`.",
                  "examples": [
                    "Seen"
                  ]
                },
                "internaldate": {
                  "type": "string",
                  "description": "When the message arrived, ISO-8601.",
                  "examples": [
                    "2026-08-03T09:14:22.000Z"
                  ]
                },
                "from_addr": {
                  "type": "string",
                  "description": "Envelope sender.",
                  "examples": [
                    "customer@example.com"
                  ]
                },
                "to_addr": {
                  "type": "string",
                  "description": "The mailbox address this copy was delivered to.",
                  "examples": [
                    "support-billing@acme.dev"
                  ]
                },
                "subject": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Subject line, null when absent.",
                  "examples": [
                    "Invoice question"
                  ]
                },
                "size": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Raw message size in bytes, when known.",
                  "examples": [
                    4821
                  ]
                }
              }
            }
          },
          "nextBefore": {
            "type": [
              "number",
              "null"
            ],
            "description": "Cursor for the next page — pass as `before`. Null when this is the last page.",
            "examples": [
              null
            ]
          }
        }
      },
      "me-response": {
        "title": "Current account",
        "type": "object",
        "required": [
          "email",
          "emailVerified",
          "plan",
          "sendingDomains"
        ],
        "additionalProperties": false,
        "properties": {
          "email": {
            "type": "string",
            "description": "The account email behind this credential.",
            "examples": [
              "owner@myapp.ai"
            ]
          },
          "emailVerified": {
            "type": "boolean",
            "description": "Whether the account email is verified. Sending is blocked until true.",
            "examples": [
              true
            ]
          },
          "plan": {
            "type": "string",
            "description": "The account's plan id.",
            "examples": [
              "free"
            ]
          },
          "sendingDomains": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Domains this credential may put in From: outbound-verified (SPF + DKIM) domains on the account. A domain-scoped key sees only its own domain.",
            "examples": [
              [
                "myapp.ai"
              ]
            ]
          }
        }
      },
      "message-detail": {
        "title": "Message detail",
        "description": "GET /api/messages/:id — the full message with its delivery history, pipeline events, per-attempt audit rows, recorded opens, and attachments.",
        "type": "object",
        "required": [
          "message",
          "actor_email",
          "actor_team_name",
          "deliveries",
          "events",
          "deliveryAttempts",
          "opens",
          "attachments"
        ],
        "properties": {
          "message": {
            "$ref": "#/components/schemas/message",
            "description": "The message itself, with bodies (text_body/html_body/headers_json) and without the list-row counters."
          },
          "actor_email": {
            "type": [
              "string",
              "null"
            ],
            "description": "When a team member sent this message as the domain owner: the acting member's email. null otherwise."
          },
          "actor_team_name": {
            "type": [
              "string",
              "null"
            ],
            "description": "When a team member sent this message as the domain owner: the team's name. null otherwise."
          },
          "deliveries": {
            "type": "array",
            "description": "Every webhook delivery of this message, newest last — retries and replays append.",
            "items": {
              "$ref": "#/components/schemas/delivery"
            }
          },
          "events": {
            "type": "array",
            "description": "The pipeline timeline for this message (received → routed → webhook attempts → retries). Metadata only, never bodies.",
            "items": {
              "type": "object",
              "required": [
                "id",
                "kind",
                "status",
                "created_at"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Event id (evt_…).",
                  "examples": [
                    "evt_8Rt5NmZx"
                  ]
                },
                "kind": {
                  "type": "string",
                  "description": "Event kind (e.g. 'email.received', 'webhook.delivered', 'webhook.failed', 'webhook.test').",
                  "examples": [
                    "webhook.delivered"
                  ]
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "ok",
                    "fail",
                    "info"
                  ],
                  "description": "Outcome facet."
                },
                "domain": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Domain the event belongs to."
                },
                "route_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Route involved, when any."
                },
                "message_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Message involved, when any."
                },
                "delivery_id": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Delivery involved, when any."
                },
                "target": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Webhook URL / forward address / recipient, when relevant."
                },
                "code": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "description": "HTTP or provider status code, when relevant.",
                  "examples": [
                    200
                  ]
                },
                "message": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Human-readable detail."
                },
                "created_at": {
                  "type": "integer",
                  "description": "When the event was recorded, in Unix epoch milliseconds (UTC).",
                  "examples": [
                    1769731200000
                  ]
                }
              }
            }
          },
          "deliveryAttempts": {
            "type": "array",
            "description": "Per-attempt audit rows (timing, request/response capture) behind the delivery inspector.",
            "items": {
              "$ref": "#/components/schemas/delivery-attempt"
            }
          },
          "opens": {
            "type": "array",
            "description": "Recorded opens for an outbound message; empty when tracking was off or nothing was opened.",
            "items": {
              "type": "object",
              "description": "One recorded open of an outbound message (present when open tracking was on). Machine opens (Apple MPP, Gmail proxy, scanners) are flagged so human counts stay meaningful."
            }
          },
          "attachments": {
            "type": "array",
            "description": "Stored attachments with signed download URLs.",
            "items": {
              "type": "object",
              "required": [
                "id",
                "filename",
                "size",
                "url"
              ],
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Attachment id."
                },
                "filename": {
                  "type": "string",
                  "description": "Original filename.",
                  "examples": [
                    "invoice-4021.pdf"
                  ]
                },
                "contentType": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "MIME type.",
                  "examples": [
                    "application/pdf"
                  ]
                },
                "size": {
                  "type": "integer",
                  "description": "Size in bytes.",
                  "examples": [
                    20841
                  ]
                },
                "contentId": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "MIME Content-ID for inline attachments."
                },
                "disposition": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "'inline' or 'attachment'."
                },
                "url": {
                  "type": "string",
                  "description": "Signed, time-limited download URL — valid 7 days, no credential needed.",
                  "examples": [
                    "https://api.mailkite.dev/att/msg_2Hk9QpVn4tLd/0?exp=1770336000&sig=9f2c"
                  ]
                }
              }
            }
          }
        }
      },
      "message": {
        "title": "Message",
        "description": "A stored message (msg_…), inbound or outbound. GET /api/messages returns list rows: the stored fields plus per-message delivery counters, without bodies. GET /api/messages/:id (see message-detail) returns the same resource with bodies (text_body/html_body/headers_json) and without the list counters.",
        "type": "object",
        "required": [
          "id",
          "user_id",
          "direction",
          "from",
          "to",
          "from_addr",
          "to_addr",
          "received_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Message id (msg_…), stable across webhook retries and replays.",
            "examples": [
              "msg_2Hk9QpVn4tLd"
            ]
          },
          "user_id": {
            "type": "string",
            "description": "Owning account (usr_…).",
            "examples": [
              "usr_7Fj3MnQw"
            ]
          },
          "route_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The route that handled an inbound message (rte_…); null for outbound sends."
          },
          "mailbox_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "The mailbox the message is anchored to for access control; null when the address isn't on an owned domain."
          },
          "direction": {
            "type": "string",
            "enum": [
              "inbound",
              "outbound"
            ],
            "description": "Whether MailKite received or sent the message."
          },
          "from": {
            "type": "object",
            "required": [
              "address",
              "name"
            ],
            "description": "Structured sender — the same shape the email.received webhook carries.",
            "properties": {
              "address": {
                "type": "string",
                "description": "Email address, from the SMTP envelope.",
                "examples": [
                  "ada@example.com"
                ]
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Display name decoded from the MIME header. null when the message carried none or the header names a different address. Sender-asserted and unverified.",
                "examples": [
                  "Ada Lovelace"
                ]
              }
            }
          },
          "to": {
            "type": "array",
            "description": "Structured recipients — the same shape the email.received webhook carries.",
            "items": {
              "type": "object",
              "required": [
                "address",
                "name"
              ],
              "description": "One recipient.",
              "properties": {
                "address": {
                  "type": "string",
                  "description": "Email address, from the SMTP envelope.",
                  "examples": [
                    "ada@example.com"
                  ]
                },
                "name": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Display name decoded from the MIME header. null when the message carried none or the header names a different address. Sender-asserted and unverified.",
                  "examples": [
                    "Ada Lovelace"
                  ]
                }
              }
            }
          },
          "from_addr": {
            "type": "string",
            "description": "Bare sender address from the SMTP envelope (MAIL FROM).",
            "examples": [
              "ada@example.com"
            ]
          },
          "to_addr": {
            "type": "string",
            "description": "Bare recipient address from the SMTP envelope (RCPT TO).",
            "examples": [
              "support@myapp.ai"
            ]
          },
          "subject": {
            "type": [
              "string",
              "null"
            ],
            "description": "Decoded subject line; null when the message had none.",
            "examples": [
              "Invoice question"
            ]
          },
          "text_body": {
            "type": [
              "string",
              "null"
            ],
            "description": "Plain-text body. Detail responses only — list rows never carry bodies."
          },
          "snippet": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 240,
            "description": "Bounded plain-text excerpt on flat message list rows. Empty for unavailable or encrypted content; omitted on detail responses. Never a full message body.",
            "examples": [
              "Thanks for confirming your order."
            ]
          },
          "html_body": {
            "type": [
              "string",
              "null"
            ],
            "description": "HTML body. Detail responses only — list rows never carry bodies."
          },
          "headers_json": {
            "type": [
              "string",
              "null"
            ],
            "description": "Stored MIME headers as a JSON string. Detail responses only."
          },
          "spf": {
            "type": [
              "string",
              "null"
            ],
            "description": "SPF verdict for an inbound message ('pass', 'fail', …); null when not checked.",
            "examples": [
              "pass"
            ]
          },
          "dkim": {
            "type": [
              "string",
              "null"
            ],
            "description": "DKIM verdict for an inbound message; null when not checked.",
            "examples": [
              "pass"
            ]
          },
          "dmarc": {
            "type": [
              "string",
              "null"
            ],
            "description": "DMARC verdict for an inbound message; null when not checked.",
            "examples": [
              "pass"
            ]
          },
          "spam": {
            "type": [
              "string",
              "null"
            ],
            "description": "Spam verdict for an inbound message ('ham', 'spam'); null when not checked."
          },
          "thread_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Conversation root id — pass it to send()'s inReplyTo to reply in-thread. null when the message carried no usable id.",
            "examples": [
              "msg_2Hk9QpVn4tLd"
            ]
          },
          "send_status": {
            "type": "string",
            "description": "Outbound send state: 'sending', 'sent', or 'failed'. Always 'sent' for inbound rows.",
            "examples": [
              "sent"
            ]
          },
          "track_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "Open-tracking token for an outbound message when tracking was on; null = off."
          },
          "received_at": {
            "type": "integer",
            "description": "When MailKite accepted the message (arrival time — stable across retries and replays), in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731200000
            ]
          },
          "size_bytes": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Stored byte size (headers + bodies; attachments sized separately).",
            "examples": [
              4821
            ]
          },
          "delivery_count": {
            "type": "integer",
            "description": "How many webhook deliveries exist for this message. List rows only.",
            "examples": [
              1
            ]
          },
          "attempts": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Max attempts across this message's deliveries. List rows only.",
            "examples": [
              1
            ]
          },
          "last_status_code": {
            "type": [
              "integer",
              "null"
            ],
            "description": "HTTP status of the most recent delivery. List rows only.",
            "examples": [
              200
            ]
          },
          "delivered_count": {
            "type": "integer",
            "description": "Deliveries in state 'delivered'. List rows only.",
            "examples": [
              1
            ]
          },
          "failed_count": {
            "type": "integer",
            "description": "Deliveries in state 'failed'. List rows only.",
            "examples": [
              0
            ]
          },
          "pending_count": {
            "type": "integer",
            "description": "Deliveries in state 'pending'. List rows only.",
            "examples": [
              0
            ]
          },
          "webhook_status": {
            "type": [
              "string",
              "null"
            ],
            "description": "Rolled-up webhook state for the list pill ('delivered', 'failed', 'pending'); null when the message has no deliveries. List rows only.",
            "examples": [
              "delivered"
            ]
          }
        }
      },
      "oauth-client-request": {
        "title": "OAuth client registration request body",
        "description": "POST /oauth/register — RFC 7591 dynamic client registration. Register once per installation (a plugin registers itself per site) and keep the returned client_id; there is no secret to store.",
        "type": "object",
        "required": [
          "redirect_uris"
        ],
        "properties": {
          "redirect_uris": {
            "type": "array",
            "description": "Where the authorization code is delivered. Every entry must be an absolute https URL — http is accepted only on loopback, for local development. A javascript: or data: URI is rejected.",
            "minItems": 1,
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "https://myapp.ai/settings/mailkite/callback"
              ]
            ]
          },
          "client_name": {
            "type": "string",
            "description": "Human-readable name shown to the user on the consent screen. Include the site or install it belongs to so someone with several connections can tell them apart.",
            "examples": [
              "MailKite for myapp.ai"
            ]
          }
        }
      },
      "oauth-client-response": {
        "title": "OAuth client registration response",
        "description": "POST /oauth/register — the registered client. Public client: there is no client_secret, and PKCE takes its place.",
        "type": "object",
        "required": [
          "client_id",
          "client_id_issued_at",
          "redirect_uris",
          "grant_types",
          "response_types",
          "token_endpoint_auth_method"
        ],
        "properties": {
          "client_id": {
            "type": "string",
            "description": "Pass this to /oauth/authorize and /oauth/token.",
            "examples": [
              "mkcli_8Kq2Vn4TwXr7Bm3d"
            ]
          },
          "client_id_issued_at": {
            "type": "integer",
            "description": "Unix seconds when the client was registered.",
            "examples": [
              1769731200
            ]
          },
          "client_name": {
            "type": "string",
            "description": "Echoed back when supplied.",
            "examples": [
              "MailKite for myapp.ai"
            ]
          },
          "redirect_uris": {
            "type": "array",
            "description": "The registered redirect URIs. The one passed to /oauth/authorize must match an entry exactly.",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "https://myapp.ai/settings/mailkite/callback"
              ]
            ]
          },
          "grant_types": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "authorization_code",
                "refresh_token"
              ]
            ]
          },
          "response_types": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "code"
              ]
            ]
          },
          "token_endpoint_auth_method": {
            "type": "string",
            "description": "Always `none` — the client is public and proves itself with PKCE.",
            "examples": [
              "none"
            ]
          }
        }
      },
      "oauth-metadata-response": {
        "title": "OAuth authorization server metadata",
        "description": "GET /.well-known/oauth-authorization-server — RFC 8414 discovery document for MailKite's authorization server. Read the endpoints from here rather than hard-coding them. Also served at /.well-known/openid-configuration for clients that probe OIDC discovery.",
        "type": "object",
        "required": [
          "issuer",
          "authorization_endpoint",
          "token_endpoint",
          "registration_endpoint",
          "scopes_supported",
          "response_types_supported",
          "grant_types_supported",
          "code_challenge_methods_supported",
          "token_endpoint_auth_methods_supported"
        ],
        "properties": {
          "issuer": {
            "type": "string",
            "description": "The authorization server's origin.",
            "examples": [
              "https://api.mailkite.dev"
            ]
          },
          "authorization_endpoint": {
            "type": "string",
            "description": "Where to send the user's browser to sign in and consent.",
            "examples": [
              "https://api.mailkite.dev/oauth/authorize"
            ]
          },
          "token_endpoint": {
            "type": "string",
            "description": "Where to exchange the authorization code (or a refresh token) for an access token.",
            "examples": [
              "https://api.mailkite.dev/oauth/token"
            ]
          },
          "registration_endpoint": {
            "type": "string",
            "description": "RFC 7591 dynamic client registration — no pre-shared client secret needed.",
            "examples": [
              "https://api.mailkite.dev/oauth/register"
            ]
          },
          "revocation_endpoint": {
            "type": "string",
            "description": "RFC 7009 token revocation.",
            "examples": [
              "https://api.mailkite.dev/oauth/revoke"
            ]
          },
          "scopes_supported": {
            "type": "array",
            "description": "Scopes this server issues. A single scope today — the token it returns acts on the whole account, so exchange it once for an API key and discard it rather than storing it long-term.",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "mcp"
              ]
            ]
          },
          "response_types_supported": {
            "type": "array",
            "description": "Authorization-code flow only.",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "code"
              ]
            ]
          },
          "grant_types_supported": {
            "type": "array",
            "description": "Grants accepted at the token endpoint.",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "authorization_code",
                "refresh_token"
              ]
            ]
          },
          "code_challenge_methods_supported": {
            "type": "array",
            "description": "PKCE is mandatory, S256 only — a plain challenge is rejected.",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "S256"
              ]
            ]
          },
          "token_endpoint_auth_methods_supported": {
            "type": "array",
            "description": "`none` — clients are public and authenticate with PKCE, not a secret.",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "none"
              ]
            ]
          }
        }
      },
      "oauth-token-request": {
        "title": "OAuth token request body",
        "description": "POST /oauth/token — exchange an authorization code for tokens, or rotate a refresh token. Accepts application/x-www-form-urlencoded (the OAuth norm) or application/json. Which fields are required depends on grant_type.",
        "type": "object",
        "required": [
          "grant_type"
        ],
        "properties": {
          "grant_type": {
            "type": "string",
            "enum": [
              "authorization_code",
              "refresh_token"
            ],
            "description": "Which exchange to perform."
          },
          "code": {
            "type": "string",
            "description": "authorization_code grant: the single-use code from the redirect. Expires quickly and is consumed on first use — a replay returns invalid_grant.",
            "examples": [
              "mkc_4Tn8Wq2Vx7Rb"
            ]
          },
          "redirect_uri": {
            "type": "string",
            "description": "authorization_code grant: must match the redirect_uri used at /oauth/authorize exactly.",
            "examples": [
              "https://myapp.ai/settings/mailkite/callback"
            ]
          },
          "client_id": {
            "type": "string",
            "description": "authorization_code grant: required. refresh_token grant: optional, but checked against the token's client when supplied.",
            "examples": [
              "mkcli_8Kq2Vn4TwXr7Bm3d"
            ]
          },
          "code_verifier": {
            "type": "string",
            "description": "authorization_code grant: the PKCE verifier whose S256 hash you sent as code_challenge. Required — there is no non-PKCE path.",
            "examples": [
              "dBjftJeZ4CVPmB92K27uhbUJU1p1r0wsUPvhBLIm3ZY"
            ]
          },
          "refresh_token": {
            "type": "string",
            "description": "refresh_token grant: the token to rotate. Single-use — the old one is revoked and a new one returned alongside the access token.",
            "examples": [
              "mkr_9Pw3Kv6Nz2Qy8Lt4"
            ]
          }
        }
      },
      "oauth-token-response": {
        "title": "OAuth token response",
        "description": "POST /oauth/token — an access token for the linked account. To finish linking, call getApiKey with this access token as the Bearer credential and store the API key it returns; then you can discard the OAuth tokens.",
        "type": "object",
        "required": [
          "access_token",
          "token_type",
          "expires_in",
          "refresh_token",
          "scope"
        ],
        "properties": {
          "access_token": {
            "type": "string",
            "description": "Bearer credential for the account that authorized you. Short-lived — see expires_in.",
            "examples": [
              "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c3JfN0ZqM01uUXcifQ.sT4x"
            ]
          },
          "token_type": {
            "type": "string",
            "description": "Always `Bearer`.",
            "examples": [
              "Bearer"
            ]
          },
          "expires_in": {
            "type": "integer",
            "description": "Access-token lifetime in seconds.",
            "examples": [
              3600
            ]
          },
          "refresh_token": {
            "type": "string",
            "description": "Single-use refresh token. Rotating it revokes this one and issues a replacement.",
            "examples": [
              "mkr_9Pw3Kv6Nz2Qy8Lt4"
            ]
          },
          "scope": {
            "type": "string",
            "description": "The granted scope. Account-wide today, which is why the recommended pattern is to exchange it for an API key immediately rather than keeping it.",
            "examples": [
              "mcp"
            ]
          }
        }
      },
      "ok-response": {
        "title": "OK",
        "description": "The uniform acknowledgement for deletes and other actions with nothing else to report.",
        "type": "object",
        "required": [
          "ok"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true,
            "description": "Always true — a non-2xx error body is returned otherwise."
          }
        }
      },
      "realtime-delivery-event": {
        "title": "Webhook delivery event",
        "description": "Part of the Realtime API (sdks/spec/realtime.json). One webhook delivery attempt as it happens — the live counterpart of a `deliveries` row. Emitted for automatic retries and manual replays alike, so watching this channel shows the whole retry ladder play out rather than only its outcome.",
        "type": "object",
        "required": [
          "id",
          "type",
          "deliveryId",
          "messageId",
          "routeId",
          "url",
          "attempt",
          "createdAt",
          "createdAtIso"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Event id, and the resume cursor for this stream.",
            "examples": [
              "evt_01J8ZQK3M4"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "webhook.delivered",
              "webhook.failed"
            ],
            "description": "Whether this attempt succeeded."
          },
          "deliveryId": {
            "type": "string",
            "description": "The delivery this attempt belongs to (dlv_…).",
            "examples": [
              "dlv_6Kp2LsWq"
            ]
          },
          "messageId": {
            "type": "string",
            "description": "The message being delivered (msg_…).",
            "examples": [
              "msg_2Hk9QpVn4tLd"
            ]
          },
          "routeId": {
            "type": "string",
            "description": "The route whose webhook was targeted (rte_…).",
            "examples": [
              "rte_4Bn8XcVm"
            ]
          },
          "url": {
            "type": "string",
            "description": "The URL that was POSTed to.",
            "examples": [
              "https://myapp.ai/hooks/mailkite"
            ]
          },
          "attempt": {
            "type": "integer",
            "description": "Which attempt this was, counting from 1.",
            "examples": [
              3
            ]
          },
          "statusCode": {
            "type": [
              "integer",
              "null"
            ],
            "description": "HTTP status returned, or null when there was no usable response (timeout, network failure, opaque redirect).",
            "examples": [
              500
            ]
          },
          "reason": {
            "type": "string",
            "enum": [
              "ok",
              "http",
              "redirect",
              "timeout",
              "network",
              "no-ack"
            ],
            "description": "Why it succeeded or failed. `no-ack` means a 2xx that did not acknowledge on a route in ack mode; `redirect` means a 3xx, which is a failure and is never followed."
          },
          "durationMs": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How long the attempt took.",
            "examples": [
              15000
            ]
          },
          "nextRetryAt": {
            "type": [
              "integer",
              "null"
            ],
            "description": "When the next automatic retry is due, in Unix epoch milliseconds (UTC). Null on success and null once the backoff ladder is exhausted — the two are told apart by `type`.",
            "examples": [
              1769731200000
            ]
          },
          "createdAt": {
            "type": "integer",
            "description": "When the attempt completed, in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731200000
            ]
          },
          "createdAtIso": {
            "type": "string",
            "format": "date-time",
            "description": "The same instant as an ISO 8601 string.",
            "examples": [
              "2026-01-30T00:00:00.000Z"
            ]
          }
        }
      },
      "realtime-error-frame": {
        "title": "Realtime error frame",
        "description": "Part of the Realtime API (sdks/spec/realtime.json). A terminal condition the client must not blindly reconnect into. Distinct from a transport drop — a dropped connection SHOULD be retried, whereas everything described here will fail again identically until something changes.",
        "type": "object",
        "required": [
          "type",
          "code",
          "message"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "error",
            "description": "Always `error`."
          },
          "code": {
            "type": "string",
            "enum": [
              "unauthorized",
              "forbidden-filter",
              "connection-limit",
              "token-expired",
              "unknown-channel"
            ],
            "description": "forbidden-filter: the credential cannot read the domain or address requested. connection-limit: too many concurrent connections for this account."
          },
          "message": {
            "type": "string",
            "description": "Human-readable explanation, safe to log.",
            "examples": [
              "This credential is not scoped to hello@example.com."
            ]
          },
          "retryable": {
            "type": "boolean",
            "description": "False for every code above except connection-limit, which becomes retryable once another connection closes.",
            "examples": [
              false
            ]
          }
        }
      },
      "realtime-gap-frame": {
        "title": "Realtime gap frame",
        "description": "Part of the Realtime API (sdks/spec/realtime.json). Sent when a resume cannot be satisfied in full — the client was disconnected past the replay window, or more events accumulated than the replay cap. It says plainly that events were missed and the stream is no longer complete, so the client can re-read over the REST API instead of assuming it saw everything.",
        "type": "object",
        "required": [
          "type",
          "missed",
          "reason"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "gap",
            "description": "Always `gap`."
          },
          "missed": {
            "type": [
              "integer",
              "null"
            ],
            "description": "How many events were skipped, or null when the count itself is unknown (the resume point fell outside retention entirely).",
            "examples": [
              512
            ]
          },
          "reason": {
            "type": "string",
            "enum": [
              "window-exceeded",
              "cap-exceeded",
              "cursor-expired"
            ],
            "description": "window-exceeded: offline longer than the replay window. cap-exceeded: more events buffered than the replay cap. cursor-expired: the Last-Event-ID is older than anything retained."
          },
          "since": {
            "type": [
              "string",
              "null"
            ],
            "description": "The event id the client asked to resume from, echoed back.",
            "examples": [
              "evt_01J8ZQK3M4"
            ]
          }
        }
      },
      "realtime-token-response": {
        "title": "Realtime token response",
        "description": "Part of the Realtime API (sdks/spec/realtime.json). A short-lived, single-use token that authorises exactly one Realtime connection. Exists because EventSource cannot set headers: without it a browser would have to put a long-lived API key in a query string, where proxies log it and browser history keeps it. Inherits the scope of the credential that minted it and can never exceed it.",
        "type": "object",
        "required": [
          "token",
          "expiresAt",
          "expiresAtIso"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "The value to pass as ?token= on the Realtime connection. Burns on first use.",
            "examples": [
              "mk_rt_9Fq2LmXc4vBn8Hs1"
            ]
          },
          "expiresAt": {
            "type": "integer",
            "description": "When the token stops being accepted, in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731500000
            ]
          },
          "expiresAtIso": {
            "type": "string",
            "format": "date-time",
            "description": "The same instant as an ISO 8601 string.",
            "examples": [
              "2026-01-30T00:05:00.000Z"
            ]
          }
        }
      },
      "register-domain-request": {
        "title": "Register domain request body",
        "type": "object",
        "required": [
          "domain",
          "contact"
        ],
        "additionalProperties": false,
        "properties": {
          "domain": {
            "type": "string"
          },
          "contact": {
            "type": "object",
            "required": [
              "firstName",
              "lastName",
              "email",
              "phone",
              "address",
              "city",
              "zip",
              "country"
            ],
            "additionalProperties": false,
            "properties": {
              "firstName": {
                "type": "string"
              },
              "lastName": {
                "type": "string"
              },
              "email": {
                "type": "string"
              },
              "phone": {
                "type": "string",
                "description": "E.164-ish, +<cc>.<number>, e.g. +1.4155551234"
              },
              "address": {
                "type": "string"
              },
              "city": {
                "type": "string"
              },
              "zip": {
                "type": "string"
              },
              "country": {
                "type": "string",
                "description": "ISO 3166-1 alpha-2, e.g. US"
              },
              "state": {
                "type": "string",
                "description": "ISO 3166-2 subdivision, e.g. US-CA"
              },
              "organization": {
                "type": "string"
              },
              "type": {
                "type": "string",
                "enum": [
                  "individual",
                  "company",
                  "association",
                  "publicbody"
                ]
              }
            }
          },
          "years": {
            "type": "integer",
            "minimum": 1
          },
          "dryRun": {
            "type": "boolean"
          }
        }
      },
      "register-domain-response": {
        "title": "Register domain response",
        "description": "POST /api/domains/register — a dry run returns just { registration, dryRun }; a real registration returns the new domain, its DNS set (auto-provisioned on the registrar when we manage its DNS), the registration reference, and the charge.",
        "type": "object",
        "required": [
          "registration"
        ],
        "properties": {
          "registration": {
            "type": "object",
            "required": [
              "status"
            ],
            "description": "The registrar's view of the registration.",
            "properties": {
              "status": {
                "type": "string",
                "description": "Registrar status (e.g. 'registered', 'pending').",
                "examples": [
                  "registered"
                ]
              },
              "reference": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Registrar order/operation reference, when provided."
              }
            }
          },
          "dryRun": {
            "type": "boolean",
            "const": true,
            "description": "Present (true) only on dry-run responses — nothing was charged or created."
          },
          "domain": {
            "$ref": "#/components/schemas/domain",
            "description": "The new domain row. Real registrations only."
          },
          "dns": {
            "type": "array",
            "description": "The mail DNS record set. Real registrations only.",
            "items": {
              "$ref": "#/components/schemas/dns-record"
            }
          },
          "dnsProvisioned": {
            "type": "boolean",
            "description": "True when the records were published automatically on the registrar's DNS."
          },
          "charge": {
            "type": "object",
            "required": [
              "amount",
              "currency",
              "status"
            ],
            "description": "What was charged. Real registrations only.",
            "properties": {
              "amount": {
                "type": "integer",
                "description": "Amount in minor units (cents).",
                "examples": [
                  1299
                ]
              },
              "currency": {
                "type": "string",
                "description": "Lowercase ISO 4217 code.",
                "examples": [
                  "usd"
                ]
              },
              "status": {
                "type": "string",
                "enum": [
                  "captured",
                  "capture_failed"
                ],
                "description": "'capture_failed' means the domain is registered and yours, but the payment capture needs follow-up."
              }
            }
          }
        }
      },
      "register-request": {
        "title": "Register request body",
        "type": "object",
        "required": [
          "email"
        ],
        "additionalProperties": false,
        "properties": {
          "email": {
            "type": "string",
            "description": "The account email. A verification link is sent to it — the account cannot send email until the address is verified, which is what makes registering safe without a password.",
            "examples": [
              "owner@myapp.ai"
            ]
          },
          "channel": {
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9_-]{0,31}$",
            "description": "Distribution-channel slug this registration came through (e.g. wordpress-plugin). Invalid values are dropped, never an error.",
            "examples": [
              "wordpress-plugin"
            ]
          },
          "ref": {
            "type": "string",
            "description": "Referral code of the account that referred this signup, when any.",
            "examples": [
              "xmbf3bd0"
            ]
          },
          "referrer": {
            "type": "string",
            "description": "First-touch landing referrer URL, when known. Invalid values are dropped, never an error.",
            "examples": [
              "https://wordpress.org/plugins/mailkite-smtp/"
            ]
          }
        }
      },
      "register-response": {
        "title": "Register result",
        "type": "object",
        "required": [
          "api_key",
          "user_id",
          "email",
          "email_verified",
          "is_new"
        ],
        "additionalProperties": false,
        "properties": {
          "api_key": {
            "type": "string",
            "description": "The new account's API key (mk_live_…) — store it now; it is only returned at registration. Sending stays blocked until the email is verified.",
            "examples": [
              "mk_live_9hK2mQx7Tw4bVnR8"
            ]
          },
          "user_id": {
            "type": "string",
            "description": "The new account id (usr_…).",
            "examples": [
              "usr_7Fj3MnQw"
            ]
          },
          "email": {
            "type": "string",
            "description": "The normalized account email the verification link was sent to.",
            "examples": [
              "owner@myapp.ai"
            ]
          },
          "email_verified": {
            "type": "boolean",
            "description": "Always false at registration — poll me() until it flips after the user clicks the link.",
            "examples": [
              false
            ]
          },
          "is_new": {
            "type": "boolean",
            "description": "Always true — an existing email returns 409 account_exists (with no credentials) instead.",
            "examples": [
              true
            ]
          }
        }
      },
      "remove-suppression-response": {
        "title": "Remove suppression result",
        "type": "object",
        "required": [
          "removed"
        ],
        "additionalProperties": false,
        "properties": {
          "removed": {
            "type": "boolean",
            "description": "Always true — removing an address that was not suppressed is a no-op success.",
            "examples": [
              true
            ]
          }
        }
      },
      "replay-request": {
        "title": "Replay request",
        "description": "Which stored deliveries to re-POST. Pass at least one of `deliveryIds`, `messageIds`, or `threadIds` — they combine, and the same endpoint is never hit twice for one message in a single call. At most 50 ids per request (threads are expanded to their messages BEFORE the cap applies); send larger selections as sequential batches.",
        "type": "object",
        "properties": {
          "deliveryIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Replay exactly these delivery rows (dlv_…), to the URL each recorded.",
            "examples": [
              [
                "dlv_6Kp2LsWq",
                "dlv_9Xr4TmBd"
              ]
            ]
          },
          "messageIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "For each message (msg_…), replay its most recent delivery per route. A message no webhook ever fired for is reported as skipped with reason `no_delivery`.",
            "examples": [
              [
                "msg_2Hk9QpVn4tLd"
              ]
            ]
          },
          "threadIds": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Conversation ids — expanded server-side to every message in the thread, then treated as `messageIds`. Lets a thread-grouped list select whole conversations.",
            "examples": [
              [
                "msg_2Hk9QpVn4tLd"
              ]
            ]
          }
        }
      },
      "replay-response": {
        "title": "Replay response",
        "description": "The outcome of a batch replay. Always 200 with per-id detail — one unreachable endpoint must not hide the outcomes of the rest — so check `ok` and `results`, not the HTTP status.",
        "type": "object",
        "required": [
          "ok",
          "requested",
          "delivered",
          "failed",
          "skipped",
          "results"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "description": "Every requested id succeeded. False if anything failed or was skipped."
          },
          "requested": {
            "type": "integer",
            "description": "How many replays this call accounted for — equal to `results.length`.",
            "examples": [
              12
            ]
          },
          "delivered": {
            "type": "integer",
            "description": "How many endpoints accepted the replay.",
            "examples": [
              10
            ]
          },
          "failed": {
            "type": "integer",
            "description": "How many were attempted and rejected (or timed out).",
            "examples": [
              1
            ]
          },
          "skipped": {
            "type": "integer",
            "description": "How many ids were never attempted — unresolvable, or a message with no delivery to replay.",
            "examples": [
              1
            ]
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/replay-result"
            },
            "description": "One row per requested id, in no guaranteed order."
          }
        }
      },
      "replay-result": {
        "title": "Replay result",
        "description": "One id's outcome inside a batch replay. Present for every id the caller asked for, including ones that were skipped — so the result list always accounts for the whole request.",
        "type": "object",
        "required": [
          "messageId",
          "routeId",
          "deliveryId",
          "ok",
          "status",
          "reason"
        ],
        "properties": {
          "messageId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The message replayed (msg_…); null when the id could not be resolved to one.",
            "examples": [
              "msg_2Hk9QpVn4tLd"
            ]
          },
          "routeId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The route whose webhook was targeted (rte_…); null when nothing was attempted.",
            "examples": [
              "rte_4Bn8XcVm"
            ]
          },
          "deliveryId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The NEW delivery row this replay appended (dlv_…); null when nothing was attempted.",
            "examples": [
              "dlv_6Kp2LsWq"
            ]
          },
          "ok": {
            "type": "boolean",
            "description": "The endpoint accepted this replay."
          },
          "status": {
            "type": [
              "integer",
              "null"
            ],
            "description": "HTTP status the endpoint returned; null when the request failed before a response, or nothing was attempted.",
            "examples": [
              200
            ]
          },
          "reason": {
            "type": [
              "string",
              "null"
            ],
            "description": "Why it did not succeed. `not_found` = no such id on this account (a foreign id reads the same way, deliberately). `no_delivery` = the message has no webhook delivery to replay. Otherwise the delivery's own classification: `http`, `timeout`, `network`, `redirect`, or `no-ack`. Null when ok.",
            "examples": [
              "http",
              "timeout",
              "not_found",
              "no_delivery"
            ]
          }
        }
      },
      "retry-delivery-response": {
        "title": "Retry delivery response",
        "description": "POST /api/deliveries/:id/retry — the outcome and the NEW delivery row the retry appended (history is append-only; the original row is untouched).",
        "type": "object",
        "required": [
          "ok",
          "status",
          "delivery"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "description": "The endpoint accepted the redelivery."
          },
          "status": {
            "type": "integer",
            "description": "HTTP status the endpoint returned (0 when the request failed before a response).",
            "examples": [
              200
            ]
          },
          "delivery": {
            "$ref": "#/components/schemas/delivery",
            "description": "The new delivery row recording this retry."
          }
        }
      },
      "route-candidate": {
        "title": "Route candidate",
        "description": "A stored inbound message one route could be asked to deliver — the preview behind replaying mail to a route defined after that mail arrived.",
        "type": "object",
        "required": [
          "id",
          "from_addr",
          "to_addr",
          "subject",
          "received_at",
          "delivered_here"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Message id (msg_…) — pass this to deliverToRoute.",
            "examples": [
              "msg_2Hk9QpVn4tLd"
            ]
          },
          "from_addr": {
            "type": "string",
            "description": "Sender address as stored.",
            "examples": [
              "customer@example.com"
            ]
          },
          "to_addr": {
            "type": "string",
            "description": "Recipient the route's pattern matched.",
            "examples": [
              "support@acme.dev"
            ]
          },
          "subject": {
            "type": [
              "string",
              "null"
            ],
            "description": "Subject line, or null when the message carried none.",
            "examples": [
              "Invoice question"
            ]
          },
          "received_at": {
            "type": "integer",
            "description": "When the message arrived, in Unix epoch milliseconds (UTC). Also the paging cursor.",
            "examples": [
              1769731200000
            ]
          },
          "delivered_here": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "1 when this route already has a delivery for the message, 0 when it has never seen it."
          }
        }
      },
      "route-candidates-response": {
        "title": "Route candidates response",
        "description": "A page of messages one route could replay, newest first.",
        "type": "object",
        "required": [
          "messages",
          "nextBefore"
        ],
        "properties": {
          "messages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/route-candidate"
            },
            "description": "Matching messages, newest first."
          },
          "nextBefore": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Cursor for the next older page — pass as `before`. null when the scan reached the end. It is the last row SCANNED, not the last returned, so a page can legitimately come back shorter than `limit` while more remain.",
            "examples": [
              1769731200000
            ]
          }
        }
      },
      "route-message-request": {
        "title": "Route-message request body",
        "type": "object",
        "required": [
          "from"
        ],
        "additionalProperties": false,
        "anyOf": [
          {
            "required": [
              "routeId"
            ]
          },
          {
            "required": [
              "address"
            ]
          }
        ],
        "properties": {
          "routeId": {
            "type": "string",
            "description": "Target route by id (rte_…). One of routeId or address is required. The route must already be registered on this account."
          },
          "address": {
            "type": "string",
            "description": "Target route by the address it matches. One of routeId or address is required."
          },
          "from": {
            "type": "string",
            "description": "Sender address recorded on the message."
          },
          "subject": {
            "type": "string",
            "description": "Optional subject line."
          },
          "text": {
            "type": "string",
            "description": "Plain-text body."
          },
          "html": {
            "type": "string",
            "description": "HTML body."
          }
        }
      },
      "route-response": {
        "title": "Route-message response body",
        "type": "object",
        "required": [
          "id",
          "routed",
          "action"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Stored message id (msg_…)."
          },
          "routed": {
            "type": "boolean",
            "description": "Whether the message was handed to the route's action."
          },
          "action": {
            "type": "string",
            "description": "The action the matched route performed: webhook, forward, agent, store, or drop."
          }
        }
      },
      "route": {
        "title": "Route",
        "description": "An inbound routing rule: which addresses it matches and what happens to matching mail (webhook POST, forward, store, drop, or an inbox agent). The per-route signing secret is returned ONCE on create and via GET /api/domains/:id/webhook/secret — list responses omit it.",
        "type": "object",
        "required": [
          "id",
          "user_id",
          "match_pattern",
          "action",
          "active",
          "ack_mode",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Route id (rte_…).",
            "examples": [
              "rte_4Bn8XcVm"
            ]
          },
          "user_id": {
            "type": "string",
            "description": "Owning account (usr_…).",
            "examples": [
              "usr_7Fj3MnQw"
            ]
          },
          "match_pattern": {
            "type": "string",
            "description": "Which recipient addresses this route matches: an exact address, `*@domain`, a prefix pattern like `ticket+*@domain`, or a `/regex/`.",
            "examples": [
              "*@myapp.ai"
            ]
          },
          "action": {
            "type": "string",
            "enum": [
              "webhook",
              "forward",
              "store",
              "drop",
              "agent"
            ],
            "description": "What happens to a matching message."
          },
          "destination": {
            "type": [
              "string",
              "null"
            ],
            "description": "Webhook URL or forward address. null for store/drop/agent actions.",
            "examples": [
              "https://myapp.ai/hooks/mailkite"
            ]
          },
          "agent_prompt": {
            "type": [
              "string",
              "null"
            ],
            "description": "Instructions for the built-in inbox agent (action 'agent' only)."
          },
          "agent_forward_to": {
            "type": [
              "string",
              "null"
            ],
            "description": "Extra addresses the inbox agent's forward tool may reach, as a JSON array string. null = owner-controlled addresses only."
          },
          "agent_context": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "message",
              "thread",
              "sender",
              null
            ],
            "description": "How much mail the inbox agent may read once the sender is verified. null = 'message' (just the inbound email)."
          },
          "active": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "1 while the route is enabled."
          },
          "ack_mode": {
            "type": "string",
            "enum": [
              "lenient",
              "ack"
            ],
            "description": "Webhook acknowledgement strictness: 'lenient' accepts any 2xx; 'ack' also requires {\"status\":\"ok\"} (or the x-mailkite-ack header) on every delivery."
          },
          "last_status": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "ok",
              "fail",
              "running",
              "timeout",
              null
            ],
            "description": "Outcome of the route's most recent action. null = never exercised."
          },
          "last_status_message": {
            "type": [
              "string",
              "null"
            ],
            "description": "Human-readable detail of the last outcome (e.g. 'HTTP 500')."
          },
          "last_status_code": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Transport status code of the last outcome, when relevant.",
            "examples": [
              200
            ]
          },
          "last_status_at": {
            "type": [
              "integer",
              "null"
            ],
            "description": "When the last outcome landed, in Unix epoch milliseconds (UTC); null when it has never happened.",
            "examples": [
              1769731200000
            ]
          },
          "consecutive_failures": {
            "type": "integer",
            "description": "Current failing streak (0 when healthy)."
          },
          "first_failed_at": {
            "type": [
              "integer",
              "null"
            ],
            "description": "When the current failing streak began, in Unix epoch milliseconds (UTC); null when it has never happened.",
            "examples": [
              1769731200000
            ]
          },
          "unhealthy_notified_at": {
            "type": [
              "integer",
              "null"
            ],
            "description": "When the owner was emailed about the current streak, in Unix epoch milliseconds (UTC); null when it has never happened.",
            "examples": [
              1769731200000
            ]
          },
          "signing_secret": {
            "type": [
              "string",
              "null"
            ],
            "description": "Per-route webhook signing secret (whsec_…). Returned on create; read later via GET /api/domains/:id/webhook/secret. null until first read lazily creates it."
          },
          "created_at": {
            "type": "integer",
            "description": "When the route was created, in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731200000
            ]
          }
        }
      },
      "rule": {
        "title": "Condition rule",
        "description": "A predicate over the enrolled contact and the trigger payload. Either a comparison — `field`, `op`, and (for binary operators) `value` — or a group: `{ \"and\": [...] }` / `{ \"or\": [...] }`. Fields live in two namespaces, `event.*` (the trigger payload) and `contact.*` (stored contact properties), plus bare `email` and `name`. This is the same operator set and shape used by Resend, Loops, and Customer.io, so an automation written against one of those ports across unchanged. A missing field is falsy for every operator except `exists` (false) and `is_empty` (true).",
        "type": "object",
        "properties": {
          "field": {
            "type": "string",
            "description": "Dotted path into the context, e.g. \"event.plan\" or \"contact.company\".",
            "examples": [
              "event.plan",
              "contact.company"
            ]
          },
          "op": {
            "type": "string",
            "enum": [
              "eq",
              "neq",
              "gt",
              "gte",
              "lt",
              "lte",
              "contains",
              "starts_with",
              "ends_with",
              "exists",
              "is_empty"
            ],
            "description": "Comparison operator. `exists` and `is_empty` take no `value`."
          },
          "value": {
            "type": [
              "string",
              "number",
              "boolean",
              "null"
            ],
            "description": "What to compare against. String and number are compared loosely, so a payload carrying \"3\" still matches a rule written against 3."
          },
          "and": {
            "type": "array",
            "description": "All of these must hold.",
            "items": {
              "$ref": "#/components/schemas/rule"
            }
          },
          "or": {
            "type": "array",
            "description": "At least one of these must hold.",
            "items": {
              "$ref": "#/components/schemas/rule"
            }
          }
        }
      },
      "schema-document": {
        "title": "JSON Schema document",
        "description": "GET /v1/schemas/:name — the named JSON Schema itself, served as application/schema+json with an absolute $id a validator can resolve and cache.",
        "type": "object"
      },
      "scoped-key": {
        "title": "Domain-scoped API key",
        "type": "object",
        "required": [
          "id",
          "user_id",
          "domain_id",
          "domain",
          "name",
          "key",
          "created_at",
          "last_used_at"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "description": "Key id (key_…).",
            "examples": [
              "key_8Rt2NvQp"
            ]
          },
          "user_id": {
            "type": "string",
            "description": "Owning account (usr_…).",
            "examples": [
              "usr_7Fj3MnQw"
            ]
          },
          "domain_id": {
            "type": "string",
            "description": "The one domain this key is scoped to (dom_…). A scoped key can send and manage only this domain — ideal for a single site or CI job.",
            "examples": [
              "dom_2VbXqTpN8rKw"
            ]
          },
          "domain": {
            "type": "string",
            "description": "Denormalized domain name, for display and fast scope checks.",
            "examples": [
              "myapp.ai"
            ]
          },
          "name": {
            "type": "string",
            "description": "User-supplied label (e.g. the site or service using it).",
            "examples": [
              "blog-wordpress"
            ]
          },
          "key": {
            "type": "string",
            "description": "The key material (mk_live_…). Returned in full on list/create — treat like a password.",
            "examples": [
              "mk_live_5sD8wYx2Kq9nBvC4"
            ]
          },
          "created_at": {
            "type": "number",
            "description": "Creation time, ms epoch.",
            "examples": [
              1753900000000
            ]
          },
          "last_used_at": {
            "type": [
              "number",
              "null"
            ],
            "description": "Last authenticated use, ms epoch — null if never used.",
            "examples": [
              null
            ]
          }
        }
      },
      "semantic-search-response": {
        "title": "Semantic docs search response",
        "type": "object",
        "required": [
          "query",
          "matches"
        ],
        "properties": {
          "query": {
            "type": "string",
            "description": "The query that was searched."
          },
          "matches": {
            "type": "array",
            "description": "Most-relevant documentation sections, best first.",
            "items": {
              "type": "object",
              "required": [
                "slug",
                "title",
                "snippet"
              ],
              "properties": {
                "slug": {
                  "type": "string",
                  "description": "Doc slug — the page lives at https://mailkite.dev/docs/<slug> (e.g. \"quickstart\", \"domains\")."
                },
                "title": {
                  "type": "string",
                  "description": "Human page title."
                },
                "section": {
                  "type": "string",
                  "description": "Doc section/category."
                },
                "snippet": {
                  "type": "string",
                  "description": "A relevant excerpt from the matched section."
                },
                "score": {
                  "type": "number",
                  "description": "Relevance score (higher is closer); absent for lexical-only matches."
                }
              }
            }
          }
        }
      },
      "send-broadcast-request": {
        "title": "Send broadcast request body",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "scheduledAt": {
            "type": "string"
          }
        }
      },
      "send-broadcast-response": {
        "title": "Send broadcast response",
        "description": "POST /api/broadcasts/:id/send — 202 with the broadcast's new state: 'scheduled' when a future scheduledAt was given, else 'sending' (fan-out continues in the background; poll GET /api/broadcasts/:id for progress).",
        "type": "object",
        "required": [
          "id",
          "status",
          "audienceCount"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "The broadcast (bct_…).",
            "examples": [
              "bct_3Jf7HkPw"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "scheduled",
              "sending"
            ],
            "description": "State the broadcast moved to."
          },
          "scheduledAt": {
            "type": "integer",
            "description": "When a scheduled broadcast will send. Present only when status is 'scheduled', in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731200000
            ]
          },
          "audienceCount": {
            "type": "integer",
            "description": "Audience size snapshotted at this moment.",
            "examples": [
              1284
            ]
          }
        }
      },
      "send-event-request": {
        "title": "Send event request body",
        "description": "One application-level fact about one of your users — `user.created`, `trial.expiring`, `payment.failed`. This is the generic trigger for sequences: a sequence whose trigger names this event enrolls the contact when it arrives, and an enrollment parked on `wait_for_event` wakes on it. Distinct from the delivery timeline at GET /api/events, which records what MailKite did with a message; this records what YOUR user did.",
        "type": "object",
        "required": [
          "name"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "description": "Event name: 1-64 characters of letters, digits, dot, dash, or underscore, starting alphanumeric. Names beginning `mailkite.`, `mailkite:`, or `email.` are reserved for platform events.",
            "examples": [
              "trial.expiring",
              "payment.failed"
            ]
          },
          "event": {
            "type": "string",
            "description": "Alias for `name`, accepted so a payload written for another provider works unchanged. `name` wins when both are given."
          },
          "email": {
            "type": "string",
            "description": "The address this event is about. Give either this or `contactId`, never both. An address we don't hold a contact for still records — the event is an observation, not a subscription, so no contact is created as a side effect."
          },
          "contactId": {
            "type": "string",
            "description": "A contact you own (ctc_…), as an alternative to `email`. Give either this or `email`, never both.",
            "examples": [
              "ctc_8Rt5NmZx"
            ]
          },
          "payload": {
            "type": "object",
            "description": "Free-form context carried with the event. Sequence steps read it as {{event.*}} for merge tags and, from conditions on, for branching.",
            "examples": [
              {
                "plan": "pro",
                "amountDue": 4200
              }
            ]
          },
          "dedupeKey": {
            "type": "string",
            "description": "Idempotency key. A second POST with the same key returns the ORIGINAL event and `duplicate: true` instead of recording a second one — so a retried webhook can never enroll the same contact twice."
          }
        }
      },
      "send-event-response": {
        "title": "Send event response body",
        "description": "Acknowledgement that the event was recorded. Answered 202: the row is written synchronously, but everything the event sets in motion (sequence enrollment, waking a parked enrollment) is not.",
        "type": "object",
        "required": [
          "id",
          "object",
          "name",
          "email",
          "duplicate"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Event id (cev_…).",
            "examples": [
              "cev_4b1c9e77"
            ]
          },
          "object": {
            "type": "string",
            "description": "Always \"event\".",
            "examples": [
              "event"
            ]
          },
          "name": {
            "type": "string",
            "description": "The recorded event name.",
            "examples": [
              "trial.expiring"
            ]
          },
          "email": {
            "type": "string",
            "description": "The address the event is about, normalized to lowercase.",
            "examples": [
              "ada@example.com"
            ]
          },
          "duplicate": {
            "type": "boolean",
            "description": "true when a `dedupeKey` matched an event already recorded — nothing new was written and `id` is the original's."
          }
        }
      },
      "send-request": {
        "title": "Send request body",
        "type": "object",
        "required": [
          "from",
          "to"
        ],
        "additionalProperties": false,
        "properties": {
          "from": {
            "type": "string",
            "description": "An address on a verified domain."
          },
          "to": {
            "description": "One recipient or a list.",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                },
                "minItems": 1
              }
            ]
          },
          "subject": {
            "type": "string",
            "description": "Required unless supplied by a template."
          },
          "html": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "templateId": {
            "type": "string",
            "description": "Send using a saved template — a user template (tpl_…) or a base template (base_…). Its subject/html/text seed the message; explicit subject/html/text here override them."
          },
          "templateData": {
            "type": "object",
            "description": "Values substituted into the template's {{merge_tags}} (e.g. {\"name\":\"Ann\"} fills {{name}}). HTML values are auto-escaped.",
            "additionalProperties": {
              "type": [
                "string",
                "number",
                "boolean",
                "null"
              ]
            }
          },
          "cc": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          },
          "bcc": {
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            ]
          },
          "replyTo": {
            "type": "string"
          },
          "inReplyTo": {
            "type": "string"
          },
          "headers": {
            "type": "object",
            "description": "Extra raw MIME headers, applied after threading headers (caller wins). Use for what the structured fields can't express — e.g. `List-Unsubscribe`, a dedup/idempotency key (`X-Entity-Ref-ID`), or a tag header (`X-Tag`). Carried on both immediate and scheduled sends.",
            "additionalProperties": {
              "type": "string"
            }
          },
          "metadata": {
            "type": "object",
            "description": "Structured metadata kept SERVER-SIDE for this send: stored on the message and echoed back on reads, but never emitted as a MIME header, so order ids, tenant keys, and internal correlation stay off the wire (that is the difference from `headers`). Scalar values only; at most 20 keys, keys ≤ 64 characters, string values ≤ 512.",
            "additionalProperties": {
              "type": [
                "string",
                "number",
                "boolean",
                "null"
              ]
            },
            "examples": [
              {
                "orderId": "ord_8812",
                "tenant": "acme"
              }
            ]
          },
          "sequence": {
            "type": "string",
            "description": "Enroll this recipient in a sequence when the send succeeds — the name or id of an ACTIVE sequence. Naming a sequence IS the consent: no trigger is involved, and an unnamed send enrols in nothing. The sequence's input is auto-injected from `templateData` and `metadata` (add `sequenceInput` for explicit params), and the message itself is readable in steps as {{trigger.from}}, {{trigger.subject}}, {{trigger.messageId}}. A name that does not exist, or a sequence that is not active, is answered on this call rather than silently ignored.",
            "examples": [
              "dunning"
            ]
          },
          "sequenceInput": {
            "type": "object",
            "description": "Explicit input for the sequence named in `sequence`, checked against its declared signature. Merged OVER the values auto-injected from `templateData` and `metadata`, so an explicit value always wins over one inferred from the message.",
            "additionalProperties": {
              "type": [
                "string",
                "number",
                "boolean",
                "null"
              ]
            },
            "examples": [
              {
                "invoiceId": "inv_1042"
              }
            ]
          },
          "attachments": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "filename"
              ],
              "properties": {
                "filename": {
                  "type": "string"
                },
                "url": {
                  "type": "string",
                  "description": "Fetch the file from this URL at send time and attach it. Any URL works; an uploadAttachment() URL is the secure, recommended choice. Prefer this over `content` for large files."
                },
                "content": {
                  "type": "string",
                  "description": "Inline file bytes as base64. Simple for tiny files, but re-uploaded on every send — use `url` (see uploadAttachment) for anything large."
                },
                "contentType": {
                  "type": "string",
                  "description": "MIME type; defaults to application/octet-stream."
                }
              }
            }
          },
          "scheduledAt": {
            "type": [
              "string",
              "number"
            ],
            "description": "Send later: ISO 8601, simple relative natural language (\"in 2 hours\"), or a ms-epoch. A future time parks the message with the scheduler — the response carries an ssnd_… id and status \"scheduled\", cancelable via DELETE /v1/scheduled/{id}. Omitted or past sends now."
          },
          "trackOpens": {
            "type": "boolean",
            "description": "Open-tracking override for this send (HTML only). Omitted → the from-domain's default applies."
          },
          "trackClicks": {
            "type": "boolean",
            "description": "Click-tracking override for this send (HTML only): http(s) links are rewritten to a signed redirect that records the click, then 302s to the destination. Omitted → the from-domain's default applies."
          }
        }
      },
      "send-response": {
        "title": "Send response body",
        "type": "object",
        "required": [
          "id",
          "status"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "scheduledAt": {
            "type": "number",
            "description": "Present on scheduled sends: when the message will fire, in ms epoch."
          }
        }
      },
      "sequence-response": {
        "title": "Sequence response body",
        "type": "object",
        "required": [
          "sequence"
        ],
        "properties": {
          "sequence": {
            "$ref": "#/components/schemas/sequence"
          },
          "enrollments": {
            "type": "object",
            "description": "Live counts keyed by enrollment status."
          },
          "triggers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/sequence-trigger"
            }
          }
        }
      },
      "sequence-step": {
        "title": "Sequence step",
        "description": "One step in a sequence. `delay` parks the contact for a duration; `send` delivers one email with content resolved at FIRE time (so merge tags see the contact as they are then, not as they were at enrollment); `condition` branches on the contact and the trigger payload; `wait_for_event` parks until an event arrives and branches on whether it did — the cancel-the-follow-up mechanism, e.g. \"chase the invoice, unless they pay\"; `branch_ai` asks a model which branch to take; `exit` ends the sequence early.",
        "type": "object",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "delay",
              "send",
              "exit",
              "condition",
              "wait_for_event",
              "branch_ai"
            ],
            "description": "Which step this is."
          },
          "for": {
            "type": "string",
            "description": "delay only: how long to wait — \"3 days\", \"90 minutes\", \"2h\". Maximum 30 days per step.",
            "examples": [
              "3 days",
              "90 minutes"
            ]
          },
          "templateId": {
            "type": "string",
            "description": "send only: a saved template (tpl_…) or base template (base_…) supplying subject/html/text. Explicit fields below override it.",
            "examples": [
              "tpl_8Rt5NmZx"
            ]
          },
          "subject": {
            "type": "string",
            "description": "send only: subject line. Required unless a templateId supplies one. Merge tags are resolved at fire time."
          },
          "html": {
            "type": "string",
            "description": "send only: HTML body."
          },
          "text": {
            "type": "string",
            "description": "send only: plain-text body."
          },
          "from": {
            "type": "string",
            "description": "send only: override the sequence's sender for this step. Must be on a verified domain."
          },
          "replyTo": {
            "type": "string",
            "description": "send only: Reply-To for this step."
          },
          "if": {
            "$ref": "#/components/schemas/rule",
            "description": "condition only: the rule to evaluate."
          },
          "then": {
            "type": "array",
            "description": "condition: steps to run when the rule holds. wait_for_event: steps to run when the event arrives. Give a wait neither arm and both outcomes simply continue to the next step.",
            "items": {
              "$ref": "#/components/schemas/sequence-step"
            }
          },
          "else": {
            "type": "array",
            "description": "condition: steps to run when the rule does not hold — a condition needs at least one of `then` or `else`. wait_for_event: steps to run when the wait times out instead.",
            "items": {
              "$ref": "#/components/schemas/sequence-step"
            }
          },
          "event": {
            "type": "string",
            "description": "wait_for_event only: the event name to wait for. Sent to POST /v1/events.",
            "examples": [
              "invoice.paid"
            ]
          },
          "timeout": {
            "type": "string",
            "description": "wait_for_event only: how long to wait before giving up. Omitted = 30 days, the maximum. Timing out takes the `else` arm, or — with no arms — simply continues to the next step. That direction is deliberate: the follow-up is the default and the event is the escape, so a never-sent event cannot silently cancel a campaign.",
            "examples": [
              "3 days"
            ]
          },
          "filter": {
            "$ref": "#/components/schemas/rule",
            "description": "wait_for_event only: wake only on an occurrence whose payload also satisfies this."
          },
          "prompt": {
            "type": "string",
            "description": "branch_ai only: what to decide. The contact and the trigger payload are supplied as context automatically — the model sees exactly what a condition or a merge tag would. Max 2000 characters.",
            "examples": [
              "Has this customer already told us they are cancelling?"
            ]
          },
          "choices": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "branch_ai only: the allowed answers, two to six of them. The model must answer with exactly one; anything else is treated as untrusted and `default` is taken.",
            "examples": [
              [
                "remind",
                "escalate",
                "stop"
              ]
            ]
          },
          "default": {
            "type": "string",
            "description": "branch_ai only, REQUIRED: which choice to take when the model is unavailable, slow, refuses, or answers something not on the list. This is what makes an AI step deterministic when the AI is not there.",
            "examples": [
              "stop"
            ]
          },
          "branches": {
            "type": "object",
            "description": "branch_ai only: steps per choice, keyed by choice name. A choice with no branch simply continues to the next step.",
            "additionalProperties": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/sequence-step"
              }
            }
          }
        }
      },
      "sequence-trigger": {
        "title": "Sequence trigger",
        "description": "A door into a sequence: when this event arrives, enroll the contact it is about. Its own resource, because a trigger is a fact about the outside world while a sequence is a program — they change on completely different rhythms. Attaching, detaching, enabling, or disabling one never bumps the sequence's version and never touches an enrollment already in flight.",
        "type": "object",
        "required": [
          "id",
          "sequenceId",
          "event",
          "enabled",
          "createdAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "trg_5f1c22ab"
            ]
          },
          "sequenceId": {
            "type": "string",
            "examples": [
              "seq_3c8f21aa"
            ]
          },
          "event": {
            "type": "string",
            "description": "The event name to match, as posted to POST /v1/events.",
            "examples": [
              "payment.failed"
            ]
          },
          "filter": {
            "type": [
              "object",
              "null"
            ],
            "description": "Only open the door when the event's payload also satisfies this rule — \"payment.failed, but only over $50\". Null means every occurrence opens it. Shape: see the `rule` schema."
          },
          "enabled": {
            "type": "boolean",
            "description": "Switch the door off without deleting or editing anything. A disabled trigger enrolls nobody; everyone already in flight carries on."
          },
          "createdAt": {
            "type": "number"
          },
          "updatedAt": {
            "type": "number"
          }
        }
      },
      "sequence": {
        "title": "Sequence",
        "description": "An automated series of emails: zero or more triggers, a declared input shape, then steps a contact walks over time. Only an `active` sequence enrolls anyone. Editing the STEPS bumps `version` and contacts already in flight keep walking the version they started on, so an edit can never make someone skip or repeat a step; nothing else about a sequence — a rename, a pause, its signature, its triggers — re-pins anybody.",
        "type": "object",
        "required": [
          "id",
          "name",
          "status",
          "steps",
          "version",
          "createdAt",
          "updatedAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Sequence id (seq_…).",
            "examples": [
              "seq_3c8f21aa"
            ]
          },
          "name": {
            "type": "string",
            "description": "The handle you reference in send({ sequence }). Unique per account; 1-64 chars of letters, digits, dot, dash, or underscore.",
            "examples": [
              "dunning"
            ]
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "active",
              "paused",
              "archived"
            ],
            "description": "Only `active` enrolls. `paused` keeps in-flight contacts parked so unpausing resumes rather than restarts; `archived` retires them."
          },
          "from": {
            "type": [
              "string",
              "null"
            ],
            "description": "Default sender for every send step, on a verified domain. Required once the sequence has an event trigger — an event-triggered enrollment has no message to inherit a sender from. A send that names the sequence always supplies one."
          },
          "input": {
            "type": [
              "object",
              "null"
            ],
            "description": "The sequence's input signature: the shape every door must supply. A sequence is a function and this is how you declare its parameters — steps then read them as {{input.field}}, and a condition can branch on them. Omit it entirely for \"anything goes\"; you should not need a schema to write a two-step drip.",
            "additionalProperties": {
              "$ref": "#/components/schemas/input-field"
            },
            "examples": [
              {
                "invoiceId": {
                  "type": "string",
                  "required": true
                },
                "amountDue": {
                  "type": "number"
                }
              }
            ]
          },
          "triggers": {
            "type": "array",
            "description": "The doors currently attached.",
            "items": {
              "$ref": "#/components/schemas/sequence-trigger"
            }
          },
          "steps": {
            "type": "array",
            "description": "The program, in order.",
            "items": {
              "$ref": "#/components/schemas/sequence-step"
            }
          },
          "version": {
            "type": "number",
            "description": "Bumped whenever trigger or steps change. Pinned per enrollment."
          },
          "reentry": {
            "type": "string",
            "description": "`once` (default) refuses to enroll a contact already in flight. `always` restarts them: the in-flight enrollment is canceled and a new one begins. `cooldown:<duration>` (e.g. \"cooldown:30 days\") re-enrolls only when that long has passed since their last enrollment.",
            "examples": [
              "once",
              "always",
              "cooldown:30 days"
            ]
          },
          "exitOn": {
            "type": [
              "object",
              "null"
            ],
            "description": "Sequence-level exits, evaluated before EVERY step. `goal` completes the enrollment — they did the thing, stop chasing, which is the difference between a dunning sequence and harassment. `cancel` abandons it.",
            "properties": {
              "goal": {
                "$ref": "#/components/schemas/rule"
              },
              "cancel": {
                "$ref": "#/components/schemas/rule"
              }
            }
          },
          "enrollments": {
            "type": "object",
            "description": "Live counts keyed by enrollment status, e.g. { \"active\": 12, \"completed\": 340 }."
          },
          "createdAt": {
            "type": "number",
            "description": "ms epoch."
          },
          "updatedAt": {
            "type": "number",
            "description": "ms epoch."
          }
        }
      },
      "set-encryption-request": {
        "title": "Set encryption request body",
        "type": "object",
        "required": [
          "publicKey"
        ],
        "additionalProperties": false,
        "properties": {
          "publicKey": {
            "type": "string",
            "description": "The public key stored bodies will be encrypted to: a PEM (SPKI) RSA key, an armored OpenPGP public key, or an X.509 certificate (S/MIME). MailKite keeps only the public key and its fingerprint — never a private key.",
            "examples": [
              "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A…\n-----END PUBLIC KEY-----"
            ]
          }
        }
      },
      "set-mailbox-flags-request": {
        "title": "Set mailbox message flags request body",
        "type": "object",
        "required": [
          "flags"
        ],
        "additionalProperties": false,
        "properties": {
          "flags": {
            "type": "string",
            "description": "The complete flag set to store, space-separated and WITHOUT leading backslashes (e.g. `Seen Flagged`). This replaces the message's flags rather than merging.",
            "examples": [
              "Seen"
            ]
          }
        }
      },
      "set-retention-request": {
        "title": "Set retention request body",
        "type": "object",
        "required": [
          "zeroRetention"
        ],
        "additionalProperties": false,
        "properties": {
          "zeroRetention": {
            "type": "boolean",
            "description": "true turns zero-retention passthrough ON: inbound mail for the domain is delivered to your webhook and never stored — no replay, no dashboard history, your endpoint holds the only copy. false restores normal retention.",
            "examples": [
              true
            ]
          }
        }
      },
      "set-tracking-webhook-request": {
        "title": "Set tracking webhook request body",
        "type": "object",
        "required": [
          "url"
        ],
        "additionalProperties": false,
        "properties": {
          "url": {
            "type": "string",
            "description": "HTTPS endpoint to receive signed email.* engagement events (sent/bounced/complained/opened/clicked). Deliberately separate from the inbound-mail webhook, so inbound consumers never see event types they don't expect."
          }
        }
      },
      "set-tracking-webhook-response": {
        "title": "Set tracking webhook response body",
        "type": "object",
        "required": [
          "domain",
          "signingSecret"
        ],
        "properties": {
          "domain": {
            "type": "object",
            "description": "The updated domain row, now carrying tracking_webhook_url."
          },
          "signingSecret": {
            "type": "string",
            "description": "The account webhook secret events are signed with (same secret as inbound deliveries) — verify the x-mailkite-signature header against it."
          }
        }
      },
      "set-webhook-events-request": {
        "title": "Set webhook events request body",
        "type": "object",
        "required": [
          "events"
        ],
        "additionalProperties": false,
        "properties": {
          "events": {
            "description": "Which email.* engagement events the domain's inbound webhook also receives: the literal \"all\", or a non-empty list of event types. Consumers distinguish deliveries by the payload's `type` (inbound mail is email.received; engagement events follow the tracking-event schema).",
            "oneOf": [
              {
                "const": "all"
              },
              {
                "type": "array",
                "minItems": 1,
                "items": {
                  "enum": [
                    "email.sent",
                    "email.bounced",
                    "email.complained",
                    "email.opened",
                    "email.clicked"
                  ]
                }
              }
            ]
          }
        }
      },
      "set-webhook-events-response": {
        "title": "Set webhook events response body",
        "type": "object",
        "required": [
          "domain",
          "signingSecret"
        ],
        "properties": {
          "domain": {
            "type": "object",
            "description": "The updated domain row, now carrying webhook_events (a JSON-encoded \"all\" or event-type list)."
          },
          "signingSecret": {
            "type": "string",
            "description": "The account webhook secret (fallback signing key). Events delivered to the inbound webhook are signed with that route's own secret when it has one, falling back to this — the same keys inbound deliveries already use, so verification code needs no change."
          }
        }
      },
      "set-webhook-request": {
        "title": "Set webhook request body",
        "type": "object",
        "required": [
          "url"
        ],
        "additionalProperties": false,
        "properties": {
          "url": {
            "type": "string"
          }
        }
      },
      "set-webhook-response": {
        "title": "Set webhook response",
        "description": "PUT /api/domains/:id/webhook — the domain, the stored webhook, and this route's signing secret (returned here so the receiver can verify x-mailkite-signature without a second call).",
        "type": "object",
        "required": [
          "domain",
          "webhookUrl",
          "ackMode",
          "routeId",
          "signingSecret"
        ],
        "properties": {
          "domain": {
            "$ref": "#/components/schemas/domain",
            "description": "The domain row (without webhook enrichment — the webhook you just set is alongside)."
          },
          "webhookUrl": {
            "type": "string",
            "description": "The stored webhook URL.",
            "examples": [
              "https://myapp.ai/hooks/mailkite"
            ]
          },
          "ackMode": {
            "type": "string",
            "enum": [
              "lenient",
              "ack"
            ],
            "description": "Acknowledgement strictness in effect."
          },
          "routeId": {
            "type": "string",
            "description": "The catch-all route (rte_…) that carries this webhook.",
            "examples": [
              "rte_4Bn8XcVm"
            ]
          },
          "signingSecret": {
            "type": "string",
            "description": "This route's signing secret (whsec_…). Store it server-side; verify every delivery.",
            "examples": [
              "whsec_9f2cQpVn4tLd8RtZ"
            ]
          }
        }
      },
      "signup-request": {
        "title": "Signup request body",
        "type": "object",
        "required": [
          "email",
          "password"
        ],
        "additionalProperties": false,
        "properties": {
          "email": {
            "type": "string",
            "description": "Account email — receives the verification link.",
            "examples": [
              "ada@example.com"
            ]
          },
          "password": {
            "type": "string",
            "description": "Account password."
          },
          "ref": {
            "type": "string",
            "description": "Referral code of the account that referred this signup, when any."
          },
          "channel": {
            "type": "string",
            "pattern": "^[a-z0-9][a-z0-9_-]{0,31}$",
            "description": "Distribution-channel slug the signup came through (e.g. wordpress-plugin) — the where to ref's who. Invalid values are dropped, never an error."
          },
          "referrer": {
            "type": "string",
            "description": "First-touch landing referrer URL (external document.referrer captured on first visit). Invalid values are dropped, never an error."
          }
        }
      },
      "start-sequence-request": {
        "title": "Start sequence request body",
        "description": "Start a sequence for one contact, directly. Use this when your code already knows WHICH sequence it wants; use sendEvent when it only knows what happened and policy should decide what reacts. The sequence must be `active`. A suppressed address, one already running on a `reentry: \"once\"` sequence, or a missing required input is refused with 409 — and the response says which.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "email": {
            "type": "string",
            "description": "The address to enroll. Give this or `contactId`.",
            "examples": [
              "ada@example.com"
            ]
          },
          "contactId": {
            "type": "string",
            "description": "A contact you own (ctc_…), as an alternative to `email`.",
            "examples": [
              "ctc_8Rt5NmZx"
            ]
          },
          "from": {
            "type": "string",
            "description": "Sender for this enrollment, when the sequence has no default (a send-triggered sequence normally inherits one from the triggering message, which a manual enroll does not have)."
          },
          "input": {
            "type": "object",
            "description": "The sequence's input, checked against its declared signature. A missing required field refuses the start and says which — you find out here rather than days later as a blank merge tag."
          },
          "cancelKey": {
            "type": "string",
            "description": "Your own key for this enrollment. Cancel later with POST /v1/enrollments/cancel and this key — no need to store our id.",
            "examples": [
              "invoice_9f2c"
            ]
          }
        }
      },
      "step-run": {
        "title": "Sequence step run",
        "description": "One executed step — the record that answers \"why didn't step 3 fire\". `detail` carries the reason: which condition failed, which branch was taken, why a send was skipped.",
        "type": "object",
        "required": [
          "id",
          "stepIndex",
          "stepType",
          "outcome",
          "ranAt"
        ],
        "properties": {
          "id": {
            "type": "string",
            "examples": [
              "run_a71f0c22"
            ]
          },
          "stepIndex": {
            "type": "number",
            "description": "Which step in the compiled program this was."
          },
          "stepType": {
            "type": "string",
            "description": "delay | send | exit | condition | wait_for_event | branch_ai."
          },
          "outcome": {
            "type": "string",
            "enum": [
              "sent",
              "skipped",
              "branched",
              "waited",
              "failed",
              "scheduled",
              "completed"
            ]
          },
          "detail": {
            "type": [
              "string",
              "null"
            ],
            "description": "Human-readable reason for the outcome."
          },
          "messageId": {
            "type": [
              "string",
              "null"
            ],
            "description": "The message a `send` step produced (msg_…)."
          },
          "ranAt": {
            "type": "number",
            "description": "ms epoch."
          }
        }
      },
      "stop-sequence-request": {
        "title": "Stop sequence request body",
        "description": "Stop whatever is chasing someone. Pass the `cancelKey` you set when starting — the point being that you never store our enrollment id, you already have an invoice or order id — or pass `sequence` and `email` together when you did not set one. Deliberately not scoped to one sequence by default: the case this exists for is \"invoice inv_1042 got paid, stop whatever was chasing it\", where the caller neither knows nor cares how many sequences that is.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "cancelKey": {
            "type": "string",
            "description": "The key you passed to startSequence. Stops every in-flight run carrying it.",
            "examples": [
              "inv_1042"
            ]
          },
          "sequence": {
            "type": "string",
            "description": "Sequence name or id. Use with `email` when no cancelKey was set.",
            "examples": [
              "dunning"
            ]
          },
          "email": {
            "type": "string",
            "description": "The contact to stop, used with `sequence`.",
            "examples": [
              "ada@example.com"
            ]
          }
        }
      },
      "stop-sequence-response": {
        "title": "Stop sequence response body",
        "description": "Always 200 with a count, never 404 — stopping something already finished is a no-op you can fire blindly from a webhook without branching on our state. Zero is a normal answer.",
        "type": "object",
        "required": [
          "stopped"
        ],
        "properties": {
          "stopped": {
            "type": "number",
            "description": "How many in-flight runs were stopped."
          },
          "cancelKey": {
            "type": "string",
            "description": "Echoed when you stopped by key."
          },
          "sequence": {
            "type": "string",
            "description": "Echoed when you stopped by sequence + email."
          },
          "email": {
            "type": "string"
          }
        }
      },
      "subdomain-availability": {
        "title": "Subdomain availability",
        "description": "GET /api/domains/subdomain/check — whether a free subdomain label can be claimed. Read-only; safe to call on every keystroke as the user edits the label.",
        "type": "object",
        "required": [
          "available"
        ],
        "properties": {
          "available": {
            "type": "boolean",
            "description": "True when the label passes validation and nobody holds it yet."
          },
          "reason": {
            "type": "string",
            "description": "Why an unavailable label was rejected — show it verbatim. Either a format rule ('At least 3 characters', 'Use a single label (no dots)', 'Use letters, numbers and hyphens only', 'That subdomain is reserved') or 'Already taken'.",
            "examples": [
              "Already taken"
            ]
          },
          "domain": {
            "type": "string",
            "description": "The full hostname the label resolves to. Absent when the label is malformed enough that no hostname could be formed.",
            "examples": [
              "swift-otter.mailk.us"
            ]
          }
        }
      },
      "subdomain-suggestion": {
        "title": "Subdomain suggestion",
        "description": "GET /api/domains/subdomain/suggest — a randomly generated, currently-unclaimed label to prefill the input with. Read-only: suggesting does not reserve the name, so claim it promptly or re-check before claiming.",
        "type": "object",
        "required": [
          "subdomain",
          "domain",
          "base"
        ],
        "properties": {
          "subdomain": {
            "type": "string",
            "description": "The suggested label on its own (adjective + noun, sometimes with a 2-digit suffix).",
            "examples": [
              "swift-otter"
            ]
          },
          "domain": {
            "type": "string",
            "description": "The full hostname the label would become — `<subdomain>.<base>`.",
            "examples": [
              "swift-otter.mailk.us"
            ]
          },
          "base": {
            "type": "string",
            "description": "The zone this subdomain would live on. Always read it from here rather than hard-coding a hostname: which zone new subdomains are handed out under changes over time, and more than one may be on offer.",
            "examples": [
              "mailk.us"
            ]
          }
        }
      },
      "suppressions-response": {
        "title": "Suppression list",
        "type": "object",
        "required": [
          "suppressions"
        ],
        "additionalProperties": false,
        "properties": {
          "suppressions": {
            "type": "array",
            "description": "Addresses this account will not send to, newest first. Sends to a suppressed address are dropped before delivery.",
            "items": {
              "type": "object",
              "required": [
                "id",
                "user_id",
                "email",
                "reason",
                "origin",
                "note",
                "created_at"
              ],
              "additionalProperties": false,
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Suppression id (sup_…).",
                  "examples": [
                    "sup_4Kp8RwQz"
                  ]
                },
                "user_id": {
                  "type": "string",
                  "description": "Owning account (usr_…).",
                  "examples": [
                    "usr_7Fj3MnQw"
                  ]
                },
                "email": {
                  "type": "string",
                  "description": "The suppressed address (normalized lowercase).",
                  "examples": [
                    "bounced@example.com"
                  ]
                },
                "reason": {
                  "type": "string",
                  "enum": [
                    "unsubscribe",
                    "hard_bounce",
                    "spam_complaint",
                    "manual"
                  ],
                  "description": "Why the address is suppressed.",
                  "examples": [
                    "hard_bounce"
                  ]
                },
                "origin": {
                  "type": "string",
                  "description": "Who created it: system (bounce/complaint webhook) or customer (manual/API).",
                  "examples": [
                    "system"
                  ]
                },
                "note": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "description": "Free-text note attached at creation — null when none.",
                  "examples": [
                    null
                  ]
                },
                "created_at": {
                  "type": "number",
                  "description": "Creation time, ms epoch.",
                  "examples": [
                    1753900000000
                  ]
                }
              }
            }
          }
        }
      },
      "template-meta": {
        "title": "Template (list row)",
        "description": "The light shape list endpoints return — identity and labelling only. The html/json/text/theme blobs are omitted here because a gallery never renders them; fetch one template by id to get the full row.",
        "type": "object",
        "required": [
          "id",
          "name",
          "category",
          "subject",
          "is_base",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Template id. User templates are tpl_…; base (premade) templates are base_<slug>. Either can be passed as send()'s templateId.",
            "examples": [
              "tpl_2Hk9QpVn4tLd"
            ]
          },
          "name": {
            "type": "string",
            "description": "Display name, as shown in the template gallery.",
            "examples": [
              "Welcome email"
            ]
          },
          "category": {
            "type": "string",
            "description": "Gallery group for base templates (Welcome, Receipt, …). Empty string for user templates, which are not grouped.",
            "examples": [
              "Welcome"
            ]
          },
          "subject": {
            "type": "string",
            "description": "Default subject line applied when sending with this template. Empty string when the template does not set one.",
            "examples": [
              "Welcome to MailKite"
            ]
          },
          "is_base": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "1 for a premade base template (world-readable, owned by the system), 0 for one of your own. Base templates cannot be edited — clone one via createTemplate's baseId.",
            "examples": [
              0
            ]
          },
          "updated_at": {
            "type": "integer",
            "description": "When the template was last modified, in Unix epoch milliseconds (UTC). List endpoints sort by this, newest first.",
            "examples": [
              1769731200000
            ]
          }
        }
      },
      "template": {
        "title": "Template",
        "description": "A saved email template, returned in full by getTemplate and createTemplate. Pass its id as send()'s templateId to use it: the stored subject/html/text seed the message, and anything you pass to send() explicitly overrides them.",
        "type": "object",
        "required": [
          "id",
          "user_id",
          "name",
          "category",
          "subject",
          "json",
          "html",
          "text",
          "theme",
          "is_base",
          "created_at",
          "updated_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Template id. User templates are tpl_…; base (premade) templates are base_<slug>.",
            "examples": [
              "tpl_2Hk9QpVn4tLd"
            ]
          },
          "user_id": {
            "type": "string",
            "description": "Owner of the template. The literal string \"system\" for base templates, which belong to no account.",
            "examples": [
              "usr_7Fj3MnQw"
            ]
          },
          "name": {
            "type": "string",
            "description": "Display name, as shown in the template gallery.",
            "examples": [
              "Welcome email"
            ]
          },
          "category": {
            "type": "string",
            "description": "Gallery group for base templates (Welcome, Receipt, …). Empty string for user templates.",
            "examples": [
              ""
            ]
          },
          "subject": {
            "type": "string",
            "description": "Default subject line applied when sending with this template. Empty string when the template does not set one.",
            "examples": [
              "Welcome to MailKite"
            ]
          },
          "json": {
            "type": "string",
            "description": "The editor document as a JSON string (TipTap format). This is the re-editable source the dashboard's template editor loads; `html` is what actually gets sent. Defaults to \"{}\".",
            "examples": [
              "{\"type\":\"doc\",\"content\":[]}"
            ]
          },
          "html": {
            "type": "string",
            "description": "The rendered, inbox-safe HTML body used when sending. Empty string for a template that has no HTML part.",
            "examples": [
              "<h1>Welcome, {{name}}</h1>"
            ]
          },
          "text": {
            "type": "string",
            "description": "Plain-text fallback body used when sending. Empty string for a template that has no text part.",
            "examples": [
              "Welcome, {{name}}"
            ]
          },
          "theme": {
            "type": "string",
            "description": "Brand tokens as a JSON string (bg, surface, primary, text, logo, …) applied when the editor re-renders the HTML. Defaults to \"{}\".",
            "examples": [
              "{\"primary\":\"#3b82f6\"}"
            ]
          },
          "is_base": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "description": "1 for a premade base template (world-readable, owned by the system), 0 for one of your own. Base templates cannot be edited — clone one via createTemplate's baseId.",
            "examples": [
              0
            ]
          },
          "created_at": {
            "type": "integer",
            "description": "When the template was created, in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731200000
            ]
          },
          "updated_at": {
            "type": "integer",
            "description": "When the template was last modified, in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731200000
            ]
          }
        }
      },
      "test-webhook-response": {
        "title": "Test webhook response",
        "description": "POST /api/domains/:id/webhook/test — outcome of the signed test delivery.",
        "type": "object",
        "required": [
          "ok",
          "status"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "description": "The endpoint accepted the test event (2xx, and acknowledged when ackMode is 'ack')."
          },
          "status": {
            "type": "integer",
            "description": "HTTP status the endpoint returned (0 when the request failed before a response).",
            "examples": [
              200
            ]
          }
        }
      },
      "tracking-event": {
        "title": "email.* tracking webhook event",
        "description": "The JSON body MailKite POSTs to a domain's tracking webhook (setTrackingWebhook) when an engagement event occurs for outbound mail. Verify the x-mailkite-signature header over the raw body (same HMAC scheme and account secret as inbound email.received deliveries) before trusting it. One event per recipient. Delivery is best-effort (single attempt).",
        "type": "object",
        "required": [
          "id",
          "type",
          "createdAt",
          "createdAtIso",
          "data"
        ],
        "additionalProperties": false,
        "properties": {
          "id": {
            "type": "string",
            "description": "Unique event id (evt_…) — use it as your idempotency key.",
            "examples": [
              "evt_2Hk9QpVn4tLd"
            ]
          },
          "type": {
            "type": "string",
            "enum": [
              "email.sent",
              "email.bounced",
              "email.complained",
              "email.opened",
              "email.clicked"
            ],
            "description": "The event type. email.delivered is reserved for a future release."
          },
          "createdAt": {
            "type": "integer",
            "description": "When the event occurred, Unix epoch milliseconds (UTC)."
          },
          "createdAtIso": {
            "type": "string",
            "format": "date-time",
            "description": "The same instant as createdAt, RFC 3339 / ISO 8601 UTC."
          },
          "data": {
            "type": "object",
            "required": [
              "messageId",
              "to"
            ],
            "additionalProperties": false,
            "properties": {
              "messageId": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The MailKite message id (msg_…, the send() response id). null when the source event can't be correlated back to a stored message — typically provider bounce/complaint notifications, which carry only providerMessageId."
              },
              "providerMessageId": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "The upstream provider's message id, when the event came from a provider notification."
              },
              "from": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "to": {
                "type": "string",
                "description": "The recipient this event is about."
              },
              "subject": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "bounce": {
                "type": "object",
                "description": "Present on email.bounced.",
                "required": [
                  "type"
                ],
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "hard",
                      "soft"
                    ]
                  },
                  "diagnostic": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "The DSN / SMTP diagnostic, when the reporting MTA supplied one."
                  }
                }
              },
              "complaint": {
                "type": "object",
                "description": "Present on email.complained.",
                "properties": {
                  "feedbackType": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "description": "The feedback-loop complaint type (e.g. abuse), when reported."
                  }
                }
              },
              "open": {
                "type": "object",
                "description": "Present on email.opened. `machine: true` marks proxy/prefetch/scanner hits (Apple MPP, Gmail image proxy, security scanners) — exclude them from human open counts.",
                "properties": {
                  "machine": {
                    "type": "boolean"
                  },
                  "machineKind": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "userAgent": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "client": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "os": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "device": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "country": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "click": {
                "type": "object",
                "description": "Present on email.clicked. Same machine flagging as opens — security scanners follow every link.",
                "required": [
                  "url"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "The destination the click resolved to."
                  },
                  "machine": {
                    "type": "boolean"
                  },
                  "machineKind": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "userAgent": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "client": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "os": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "device": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "country": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        }
      },
      "trigger-response": {
        "title": "Trigger response body",
        "type": "object",
        "required": [
          "trigger"
        ],
        "properties": {
          "trigger": {
            "$ref": "#/components/schemas/sequence-trigger"
          }
        }
      },
      "update-app-password-request": {
        "title": "Update app password request body",
        "description": "Every field is optional — send only what changes. The domain is deliberately absent: a password is bound to its domain for life, because repointing a live credential would hand its holder mail they were never granted. Create a new password for another domain.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "address": {
            "type": "string",
            "description": "New local-part pattern — `*` (every address), `hello`, `support-*`, `*-agent`. No `@`: the domain can't change.",
            "examples": [
              "support-*"
            ]
          },
          "protocols": {
            "type": "array",
            "description": "Replaces the existing set. Must be a non-empty subset of `[\"imap\",\"api\"]`.",
            "items": {
              "type": "string",
              "enum": [
                "imap",
                "api"
              ]
            },
            "examples": [
              [
                "imap",
                "api"
              ]
            ]
          },
          "label": {
            "type": "string",
            "description": "New name shown in the dashboard.",
            "examples": [
              "support inbox agent"
            ]
          }
        }
      },
      "update-broadcast-request": {
        "title": "Update broadcast request body (draft edits; all fields optional)",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string"
          },
          "from": {
            "type": "string"
          },
          "replyTo": {
            "type": "string"
          },
          "subject": {
            "type": "string"
          },
          "preview": {
            "type": "string"
          },
          "audience": {
            "type": "object",
            "additionalProperties": false,
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "all",
                  "list",
                  "filter"
                ]
              },
              "id": {
                "type": "string"
              },
              "query": {
                "type": "string"
              }
            }
          },
          "templateId": {
            "type": "string"
          },
          "html": {
            "type": "string"
          },
          "text": {
            "type": "string"
          },
          "footerAddress": {
            "type": "string"
          }
        }
      },
      "update-list-request": {
        "title": "Update list request body",
        "type": "object",
        "required": [
          "name"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string"
          }
        }
      },
      "update-sequence-request": {
        "title": "Update sequence request body",
        "description": "Every field is optional; omitted fields are left alone. Changing `trigger` or `steps` bumps the sequence's version, and contacts already in flight keep walking the version they started on.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "active",
              "paused",
              "archived"
            ]
          },
          "from": {
            "type": "string"
          },
          "input": {
            "type": [
              "object",
              "null"
            ],
            "description": "The sequence's input signature: the shape every door must supply. A sequence is a function and this is how you declare its parameters — steps then read them as {{input.field}}, and a condition can branch on them. Omit it entirely for \"anything goes\"; you should not need a schema to write a two-step drip.",
            "additionalProperties": {
              "$ref": "#/components/schemas/input-field"
            },
            "examples": [
              {
                "invoiceId": {
                  "type": "string",
                  "required": true
                },
                "amountDue": {
                  "type": "number"
                }
              }
            ]
          },
          "triggers": {
            "type": "array",
            "description": "Doors into this sequence. A bare string is shorthand for { \"event\": \"<name>\" }. Replacing the set never bumps the sequence's version and never touches an enrollment in flight. A sequence with no triggers is enrolled only by naming it on a send or calling enroll — which is what \"manual\" used to mean.",
            "items": {
              "oneOf": [
                {
                  "type": "string"
                },
                {
                  "$ref": "#/components/schemas/create-trigger-request"
                }
              ]
            },
            "examples": [
              [
                "payment.failed",
                "invoice.overdue"
              ]
            ]
          },
          "steps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/sequence-step"
            }
          },
          "reentry": {
            "type": "string",
            "description": "`once` (default) refuses to enroll a contact already in flight. `always` restarts them: the in-flight enrollment is canceled and a new one begins. `cooldown:<duration>` (e.g. \"cooldown:30 days\") re-enrolls only when that long has passed since their last enrollment.",
            "examples": [
              "once",
              "always",
              "cooldown:30 days"
            ]
          },
          "exitOn": {
            "type": [
              "object",
              "null"
            ],
            "description": "Sequence-level exits, evaluated before EVERY step. `goal` completes the enrollment — they did the thing, stop chasing, which is the difference between a dunning sequence and harassment. `cancel` abandons it.",
            "properties": {
              "goal": {
                "$ref": "#/components/schemas/rule"
              },
              "cancel": {
                "$ref": "#/components/schemas/rule"
              }
            }
          }
        }
      },
      "update-trigger-request": {
        "title": "Update trigger request body",
        "description": "Every field optional. Toggling `enabled` is the ad-hoc lever: it stops future enrollments immediately and leaves everyone in flight alone.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "event": {
            "type": "string"
          },
          "filter": {
            "$ref": "#/components/schemas/rule"
          },
          "enabled": {
            "type": "boolean"
          }
        }
      },
      "upload-attachment-request": {
        "title": "Upload attachment request body",
        "description": "Provide the file ONE of four ways: `path` (a local file the SDK/CLI/MCP reads and streams as raw bytes), `url` (a remote file MailKite fetches and re-hosts), `content` (base64-encoded bytes), or — at the HTTP layer — the raw file bytes as the POST body with `?filename=`. `path`/raw-bytes go up as a real binary upload (like an S3/R2 PUT); `url`/`content` go up as JSON.",
        "type": "object",
        "additionalProperties": false,
        "properties": {
          "filename": {
            "type": "string",
            "description": "The file's name, e.g. \"invoice.pdf\". Shown to recipients on download. Optional when it can be derived from `path` or `url`."
          },
          "path": {
            "type": "string",
            "description": "Local filesystem path to the file. Read client-side by the CLI, SDKs, and the local MCP server, then uploaded as raw bytes. Not available on the hosted MCP (no filesystem)."
          },
          "url": {
            "type": "string",
            "description": "A remote http(s) URL. MailKite fetches it and re-hosts the bytes under your account. Max 25 MB."
          },
          "content": {
            "type": "string",
            "description": "The file bytes, base64-encoded. The lowest-common-denominator fallback when you can't send a path, URL, or raw bytes."
          },
          "contentType": {
            "type": "string",
            "description": "MIME type, e.g. \"application/pdf\". Defaults to application/octet-stream (or is inferred from the file extension / fetched response)."
          },
          "retentionDays": {
            "type": "integer",
            "enum": [
              7,
              30,
              90,
              365
            ],
            "description": "How long the file (and its signed URL) stays valid. One of 7, 30, 90, 365. Defaults to 7."
          }
        }
      },
      "upload-attachment-response": {
        "title": "Upload attachment response",
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "Storage key for the uploaded file."
          },
          "url": {
            "type": "string",
            "description": "Secure, time-limited URL. Pass it as an attachment's `url` in send(), or link it inline in your HTML."
          },
          "filename": {
            "type": "string"
          },
          "contentType": {
            "type": "string"
          },
          "size": {
            "type": "integer",
            "description": "Stored size in bytes."
          },
          "expiresAt": {
            "type": "string",
            "description": "ISO-8601 timestamp when the file and URL expire."
          }
        }
      },
      "usage-response": {
        "title": "Current billing-period usage",
        "type": "object",
        "required": [
          "plan",
          "emails",
          "aiActions",
          "meteringEnabled",
          "meters",
          "overage"
        ],
        "additionalProperties": false,
        "properties": {
          "plan": {
            "type": "string",
            "description": "The account's plan id.",
            "examples": [
              "free",
              "pro"
            ]
          },
          "emails": {
            "type": "object",
            "description": "Email volume this billing period (inbound + outbound share one bucket).",
            "required": [
              "used",
              "included"
            ],
            "additionalProperties": false,
            "properties": {
              "used": {
                "type": "number",
                "description": "Emails counted so far this period.",
                "examples": [
                  1240
                ]
              },
              "included": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "The plan's included monthly emails — null means unlimited.",
                "examples": [
                  3000
                ]
              }
            }
          },
          "aiActions": {
            "type": "object",
            "description": "AI agent actions this period (pay-as-you-go — no included bucket).",
            "required": [
              "used"
            ],
            "additionalProperties": false,
            "properties": {
              "used": {
                "type": "number",
                "description": "AI actions counted so far this period.",
                "examples": [
                  12
                ]
              }
            }
          },
          "meteringEnabled": {
            "type": "boolean",
            "description": "True when overage metering is active (billing configured and a subscription exists).",
            "examples": [
              false
            ]
          },
          "trial": {
            "type": [
              "object",
              "null"
            ],
            "description": "Bounded trial allowance and expiry for an account that has started a trial; null when no trial has been started. Included for active and historical trials.",
            "required": [
              "active",
              "endsAt",
              "emails",
              "dailyEmails",
              "aiActions"
            ],
            "additionalProperties": false,
            "properties": {
              "active": {
                "type": "boolean",
                "description": "Whether the account is currently in its trial window."
              },
              "endsAt": {
                "type": [
                  "number",
                  "null"
                ],
                "description": "Trial expiry as epoch milliseconds, or null if unavailable."
              },
              "emails": {
                "type": "number",
                "description": "Total emails included in the trial."
              },
              "dailyEmails": {
                "type": "number",
                "description": "Daily email allowance during the trial."
              },
              "aiActions": {
                "type": "number",
                "description": "AI actions included in the trial."
              }
            }
          },
          "meters": {
            "type": "array",
            "description": "The usage meters billed on this account.",
            "items": {
              "type": "string"
            },
            "examples": [
              [
                "email",
                "ai_actions"
              ]
            ]
          },
          "overage": {
            "type": "object",
            "description": "Overage state — drives the dashboard notice and the plugin quota bar.",
            "required": [
              "over",
              "metered",
              "blocked",
              "status",
              "code"
            ],
            "additionalProperties": false,
            "properties": {
              "over": {
                "type": "boolean",
                "description": "Past the plan's included email bucket this period.",
                "examples": [
                  false
                ]
              },
              "metered": {
                "type": "boolean",
                "description": "Overage is being metered and billed normally.",
                "examples": [
                  false
                ]
              },
              "blocked": {
                "type": "boolean",
                "description": "Sending is blocked until the payment card is fixed.",
                "examples": [
                  false
                ]
              },
              "status": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Card-gate status when in overage (ok | failed …) — null outside overage.",
                "examples": [
                  null
                ]
              },
              "code": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Machine-readable reason code when blocked — null otherwise.",
                "examples": [
                  null
                ]
              }
            }
          }
        }
      },
      "verify-domain-response": {
        "title": "Verify domain response",
        "description": "POST /api/domains/:id/verify — fresh DNS check results, the resulting status, and the sending provider's enablement outcome.",
        "type": "object",
        "required": [
          "status",
          "checks",
          "checkedAt",
          "sending"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "verified",
              "pending"
            ],
            "description": "'verified' once MX resolves; SPF/DKIM/DMARC refine sending but don't gate inbound."
          },
          "checks": {
            "type": "object",
            "required": [
              "mx",
              "spf",
              "dkim",
              "dmarc"
            ],
            "description": "Per-record verification results from this check.",
            "properties": {
              "mx": {
                "type": "boolean",
                "description": "MX resolves to MailKite."
              },
              "spf": {
                "type": "boolean",
                "description": "SPF TXT record is in place."
              },
              "dkim": {
                "type": "boolean",
                "description": "DKIM records are accepted by the sending provider."
              },
              "dmarc": {
                "type": "boolean",
                "description": "A DMARC policy is published."
              }
            }
          },
          "checkedAt": {
            "type": "integer",
            "description": "When this check ran, in Unix epoch milliseconds (UTC).",
            "examples": [
              1769731200000
            ]
          },
          "sending": {
            "type": "object",
            "required": [
              "ok"
            ],
            "description": "Outcome of enabling outbound sending with the domain's provider (attempted once SPF+DKIM pass).",
            "properties": {
              "ok": {
                "type": "boolean",
                "description": "The provider call succeeded."
              },
              "enabled": {
                "type": "boolean",
                "description": "Final enabled state reported by the provider."
              },
              "dkimVerified": {
                "type": "boolean",
                "description": "Whether the provider accepted the DKIM records (can lead `enabled`)."
              },
              "alreadyEnabled": {
                "type": "boolean",
                "description": "True when the domain was already registered with the provider (no-op)."
              },
              "providerId": {
                "type": "string",
                "description": "Provider-specific domain identifier, when reported."
              },
              "error": {
                "type": "string",
                "description": "Why the provider call failed, when it did."
              }
            }
          }
        }
      },
      "verify-webhook-request": {
        "title": "Verify webhook request",
        "description": "Inputs to verifyWebhook — a local HMAC check, no API call.",
        "type": "object",
        "required": [
          "payload",
          "signature",
          "secret"
        ],
        "additionalProperties": false,
        "properties": {
          "payload": {
            "type": "string",
            "description": "The raw, unparsed webhook request body — the exact bytes you received."
          },
          "signature": {
            "type": "string",
            "description": "The `x-mailkite-signature` header value, e.g. `t=1750000000000,v1=4f1a9c…`."
          },
          "secret": {
            "type": "string",
            "description": "Your webhook signing secret (from the dashboard)."
          },
          "toleranceMs": {
            "type": "integer",
            "minimum": 0,
            "description": "Reject events whose timestamp is more than this many milliseconds old, to block replays. Defaults to 300000 (5 minutes). Pass 0 to disable the freshness check."
          }
        }
      },
      "webhook-secret-response": {
        "title": "Webhook secret response",
        "description": "GET /api/domains/:id/webhook/secret — this domain's webhook signing secret. Server-side only; never exposed to MCP or browser clients.",
        "type": "object",
        "required": [
          "secret"
        ],
        "properties": {
          "secret": {
            "type": "string",
            "description": "The per-route signing secret (whsec_…) for the domain's catch-all webhook.",
            "examples": [
              "whsec_9f2cQpVn4tLd8RtZ"
            ]
          }
        }
      }
    }
  },
  "x-mailkite-contract": {
    "generatedFrom": "sdks/spec/api.json",
    "operationCount": 102
  }
}