{
  "openapi": "3.0.4",
  "info": {
    "title": "Postilio.Api | v1",
    "version": "1.0.0"
  },
  "paths": {
    "/v1/emails": {
      "post": {
        "tags": [
          "Emails"
        ],
        "summary": "Send an email",
        "description": "Needs the `emails:send` scope. Stores one message per recipient and answers 202 with their ids, in the order of `to`. A recipient on the suppression list is accepted but not sent, and listed in `suppressed`. The sender's domain must be verified. A test key (`pk_test_`) never sends: its messages get simulated events. In the sandbox, live mail goes only to the organization's members and verified domains (422 `sandbox_recipient_not_allowed`), within a daily and a per-minute limit (429 with `Retry-After`).",
        "operationId": "SendEmail",
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "description": "1 to 256 characters. A repeat within 24 hours answers as the first request did, without sending again.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendEmailRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendEmailResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Entity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/emails/{id}": {
      "get": {
        "tags": [
          "Emails"
        ],
        "summary": "Get an email and its events",
        "description": "Needs the `emails:read` scope. A test key finds only test messages and a live key only live ones; the other mode's are 404.",
        "operationId": "GetEmail",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found"
          }
        }
      }
    },
    "/v1/domains": {
      "post": {
        "tags": [
          "Domains"
        ],
        "summary": "Add a sending domain",
        "description": "Needs the `domains:manage` scope. Answers the domain as `pending`, with the two CNAME records to create in its DNS. postilio.eu and names under it cannot be added. 409 `domain_exists` when the project has it already.",
        "operationId": "CreateDomain",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDomainRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DomainResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "Domains"
        ],
        "summary": "List sending domains",
        "description": "Needs the `domains:manage` scope.",
        "operationId": "ListDomains",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DomainList"
                }
              }
            }
          }
        }
      }
    },
    "/v1/domains/{id}": {
      "get": {
        "tags": [
          "Domains"
        ],
        "summary": "Get a sending domain",
        "description": "Needs the `domains:manage` scope.",
        "operationId": "GetDomain",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DomainResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found"
          }
        }
      },
      "delete": {
        "tags": [
          "Domains"
        ],
        "summary": "Remove a sending domain",
        "description": "Needs the `domains:manage` scope. Mail from the domain stops at once.",
        "operationId": "DeleteDomain",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "404": {
            "description": "Not Found"
          }
        }
      }
    },
    "/v1/domains/{id}/check": {
      "post": {
        "tags": [
          "Domains"
        ],
        "summary": "Check a domain's DNS records now",
        "description": "Needs the `domains:manage` scope. Checks now instead of at the next periodic check (every 10 minutes) and answers the domain's new state. Once a minute per domain: 429 `too_many_checks` with `Retry-After`. 503 `dns_unavailable` when DNS gave no answer.",
        "operationId": "CheckDomain",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DomainResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found"
          },
          "429": {
            "description": "Too Many Requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "503": {
            "description": "Service Unavailable",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/suppressions": {
      "get": {
        "tags": [
          "Suppressions"
        ],
        "summary": "List suppressed addresses",
        "description": "Needs the `suppressions:manage` scope. Newest first; pass `next` as `before` for the next page.",
        "operationId": "ListSuppressions",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "description": "Part of an address.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "reason",
            "in": "query",
            "description": "`hard_bounce`, `complaint` or `manual`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "before",
            "in": "query",
            "description": "The `next` of the previous page.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "1 to 100; 50 when left out.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuppressionList"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Suppressions"
        ],
        "summary": "Suppress an address",
        "description": "Needs the `suppressions:manage` scope. Adds the address with reason `manual`. 409 `address_already_suppressed` when it is on the list already.",
        "operationId": "CreateSuppression",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSuppressionRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuppressionResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/suppressions/{id}": {
      "delete": {
        "tags": [
          "Suppressions"
        ],
        "summary": "Remove a suppressed address",
        "description": "Needs the `suppressions:manage` scope. A complaint is removed only with a reason, `{\"reason\": \"…\"}` of 10 to 500 characters; without one the answer is 422 `reason_required`. A hard bounce or manual entry needs no body.",
        "operationId": "DeleteSuppression",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RemoveSuppressionRequest"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "No Content"
          },
          "404": {
            "description": "Not Found"
          },
          "422": {
            "description": "Unprocessable Entity",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List webhook endpoints",
        "description": "Needs the `webhooks:manage` scope.",
        "operationId": "ListWebhookEndpoints",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointList"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Add a webhook endpoint",
        "description": "Needs the `webhooks:manage` scope. The answer holds the signing secret, the only time it is shown. At most 10 endpoints per project: 409 `webhook_endpoint_limit_reached`.",
        "operationId": "CreateWebhookEndpoint",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookEndpointRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreatedWebhookEndpoint"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Get a webhook endpoint",
        "description": "Needs the `webhooks:manage` scope.",
        "operationId": "GetWebhookEndpoint",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found"
          }
        }
      },
      "patch": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Change a webhook endpoint",
        "description": "Needs the `webhooks:manage` scope. Only the fields you send change. `paused: false` resumes a paused endpoint.",
        "operationId": "UpdateWebhookEndpoint",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWebhookEndpointRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpointResponse"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found"
          }
        }
      },
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Remove a webhook endpoint",
        "description": "Needs the `webhooks:manage` scope.",
        "operationId": "DeleteWebhookEndpoint",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "404": {
            "description": "Not Found"
          }
        }
      }
    },
    "/v1/webhooks/{id}/secret": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Rotate the signing secret",
        "description": "Needs the `webhooks:manage` scope. Answers the new secret once. The old one keeps signing as well for 24 hours.",
        "operationId": "RotateWebhookSecret",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RotatedWebhookSecret"
                }
              }
            }
          },
          "404": {
            "description": "Not Found"
          }
        }
      }
    },
    "/v1/webhooks/{id}/test": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Send a test event",
        "description": "Needs the `webhooks:manage` scope. One `webhook.test.v1` delivery, tried once without retries, also to a paused endpoint.",
        "operationId": "SendWebhookTestEvent",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Accepted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDeliveryResponse"
                }
              }
            }
          },
          "404": {
            "description": "Not Found"
          }
        }
      }
    },
    "/v1/webhooks/{id}/deliveries": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "summary": "List an endpoint's deliveries",
        "description": "Needs the `webhooks:manage` scope. Newest first; pass `next` as `before` for the next page.",
        "operationId": "ListWebhookDeliveries",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "`pending`, `delivered` or `failed`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "before",
            "in": "query",
            "description": "The `next` of the previous page.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "1 to 100; 50 when left out.",
            "schema": {
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDeliveryList"
                }
              }
            }
          },
          "400": {
            "description": "Bad Request",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/HttpValidationProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found"
          }
        }
      }
    },
    "/v1/webhooks/{id}/deliveries/{deliveryId}/retry": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "summary": "Retry a delivery",
        "description": "Needs the `webhooks:manage` scope. One more attempt now, with the same id and payload. 409 `delivery_still_pending` while it is still being retried, `endpoint_paused` while the endpoint is paused.",
        "operationId": "RetryWebhookDelivery",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "deliveryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "202": {
            "description": "Accepted"
          },
          "404": {
            "description": "Not Found"
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CreateDomainRequest": {
        "required": [
          "name"
        ],
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "A host name of at least two labels, such as `mail.example.com`; not an IP address, not under postilio.eu.",
            "nullable": true
          }
        }
      },
      "CreatedWebhookEndpoint": {
        "required": [
          "endpoint",
          "secret"
        ],
        "type": "object",
        "properties": {
          "endpoint": {
            "$ref": "#/components/schemas/WebhookEndpointResponse"
          },
          "secret": {
            "type": "string",
            "description": "The signing secret (whsec_…). Shown once: it is stored encrypted and never returned again."
          }
        }
      },
      "CreateSuppressionRequest": {
        "required": [
          "address"
        ],
        "type": "object",
        "properties": {
          "address": {
            "type": "string",
            "description": "One bare address, at most 254 characters.",
            "nullable": true
          }
        }
      },
      "CreateWebhookEndpointRequest": {
        "required": [
          "url",
          "events"
        ],
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "description": "An absolute `https://` URL on a public address, at most 2048 characters, without credentials or a fragment.",
            "nullable": true
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "One or more of: accepted, queued, delivered, deferred, bounced, expired, complained, suppressed.",
            "nullable": true
          },
          "description": {
            "type": "string",
            "description": "Optional; at most 200 characters.",
            "nullable": true
          },
          "mode": {
            "type": "string",
            "description": "`live` (the default) or `test`.",
            "nullable": true
          }
        }
      },
      "DnsRecord": {
        "required": [
          "type",
          "name",
          "value",
          "status"
        ],
        "type": "object",
        "properties": {
          "type": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "value": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "found, missing, or unknown until the first check."
          }
        }
      },
      "DomainList": {
        "required": [
          "data"
        ],
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DomainResponse"
            }
          }
        },
        "description": "A list in an object, so paging can be added without breaking clients."
      },
      "DomainResponse": {
        "required": [
          "id",
          "name",
          "status",
          "checkedAt",
          "records",
          "createdAt",
          "addedBy",
          "failingSince",
          "sent30d"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "name": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "pending, verified or failing."
          },
          "checkedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "records": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DnsRecord"
            }
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "addedBy": {
            "type": "string",
            "description": "The name of the dashboard user who added it; null when it was added with an API key.",
            "nullable": true
          },
          "failingSince": {
            "type": "string",
            "description": "Since when its records are missing, while it is failing.",
            "format": "date-time",
            "nullable": true
          },
          "sent30d": {
            "type": "integer",
            "description": "Live messages sent from it today and the 29 days before (UTC); test and suppressed ones not.",
            "format": "int32"
          }
        }
      },
      "EmailAttachment": {
        "required": [
          "fileName",
          "contentType",
          "content"
        ],
        "type": "object",
        "properties": {
          "fileName": {
            "type": "string",
            "nullable": true
          },
          "contentType": {
            "type": "string",
            "description": "A MIME type such as `application/pdf`, optionally with parameters.",
            "nullable": true
          },
          "content": {
            "type": "string",
            "description": "The file, base64-encoded.",
            "nullable": true
          },
          "contentId": {
            "type": "string",
            "description": "Makes the attachment inline; the HTML refers to it as `cid:` plus this value.",
            "nullable": true
          }
        }
      },
      "EmailDetails": {
        "required": [
          "id",
          "status",
          "from",
          "to",
          "subject",
          "tag",
          "acceptedAt",
          "test",
          "events",
          "via"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string"
          },
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "subject": {
            "type": "string",
            "nullable": true
          },
          "tag": {
            "type": "string",
            "nullable": true
          },
          "acceptedAt": {
            "type": "string",
            "format": "date-time"
          },
          "test": {
            "type": "boolean"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EmailEvent"
            }
          },
          "via": {
            "type": "string",
            "description": "`api` or `smtp`: how the message was submitted."
          }
        }
      },
      "EmailEvent": {
        "required": [
          "type",
          "occurredAt",
          "smtpCode",
          "response"
        ],
        "type": "object",
        "properties": {
          "type": {
            "type": "string"
          },
          "occurredAt": {
            "type": "string",
            "format": "date-time"
          },
          "smtpCode": {
            "type": "integer",
            "format": "int16",
            "nullable": true
          },
          "response": {
            "type": "string",
            "nullable": true
          },
          "attempt": {
            "type": "integer",
            "format": "int16",
            "nullable": true
          },
          "remoteHost": {
            "type": "string",
            "nullable": true
          },
          "enhancedCode": {
            "type": "string",
            "nullable": true
          },
          "classification": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "ErrorResponse": {
        "required": [
          "error"
        ],
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        }
      },
      "HttpValidationProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "nullable": true
          },
          "title": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "detail": {
            "type": "string",
            "nullable": true
          },
          "instance": {
            "type": "string",
            "nullable": true
          },
          "errors": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          }
        }
      },
      "RemoveSuppressionRequest": {
        "type": "object",
        "properties": {
          "reason": {
            "type": "string",
            "description": "Required, at least 10 characters, when the entry is a complaint; kept with the removal.",
            "nullable": true
          }
        }
      },
      "RotatedWebhookSecret": {
        "required": [
          "secret",
          "previousSecretExpiresAt"
        ],
        "type": "object",
        "properties": {
          "secret": {
            "type": "string",
            "description": "The new signing secret, shown once."
          },
          "previousSecretExpiresAt": {
            "type": "string",
            "description": "Until then deliveries are signed with the old secret as well.",
            "format": "date-time"
          }
        }
      },
      "SendEmailRequest": {
        "required": [
          "from",
          "to",
          "subject"
        ],
        "type": "object",
        "properties": {
          "from": {
            "type": "string",
            "description": "An address on a verified domain of the key's project, optionally with a display name (without '@' or ',').",
            "nullable": true
          },
          "to": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "1 to 50 bare addresses, without display names; each gets a message of its own.",
            "nullable": true
          },
          "subject": {
            "type": "string",
            "description": "At most 998 characters, without control characters.",
            "nullable": true
          },
          "text": {
            "type": "string",
            "description": "The plain-text body; `text`, `html` or both.",
            "nullable": true
          },
          "html": {
            "type": "string",
            "description": "The HTML body; `text`, `html` or both.",
            "nullable": true
          },
          "tag": {
            "type": "string",
            "description": "Optional; at most 64 letters, digits, '-' or '_'.",
            "nullable": true
          },
          "replyTo": {
            "type": "string",
            "description": "Optional; one address, with or without a display name.",
            "nullable": true
          },
          "attachments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EmailAttachment"
            },
            "description": "Optional; one with a `contentId` is inline, for `cid:` references in the HTML.",
            "nullable": true
          }
        }
      },
      "SendEmailResponse": {
        "required": [
          "ids",
          "suppressed"
        ],
        "type": "object",
        "properties": {
          "ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            },
            "description": "One message per recipient, in the order of `to`."
          },
          "suppressed": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Recipients on the project's suppression list: accepted with status `suppressed`, not sent."
          }
        }
      },
      "SuppressionList": {
        "required": [
          "data",
          "next"
        ],
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SuppressionResponse"
            }
          },
          "next": {
            "type": "string",
            "description": "Pass as `before` for the next page; null on the last page.",
            "format": "uuid",
            "nullable": true
          }
        }
      },
      "SuppressionResponse": {
        "required": [
          "id",
          "address",
          "reason",
          "detail",
          "sourceMessageId",
          "createdAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "address": {
            "type": "string"
          },
          "reason": {
            "type": "string",
            "description": "`hard_bounce`, `complaint` or `manual`."
          },
          "detail": {
            "type": "string",
            "description": "The remote server's answer for a bounce, or who added it.",
            "nullable": true
          },
          "sourceMessageId": {
            "type": "string",
            "description": "The message that caused it; it may already be gone from the log.",
            "format": "uuid",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "UpdateWebhookEndpointRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "nullable": true
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "nullable": true
          },
          "description": {
            "type": "string",
            "description": "An empty string removes it.",
            "nullable": true
          },
          "paused": {
            "type": "boolean",
            "description": "true pauses the endpoint, false resumes it.",
            "nullable": true
          }
        },
        "description": "Every field is optional; what is left out stays as it is."
      },
      "WebhookDeliveryList": {
        "required": [
          "data",
          "next"
        ],
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookDeliveryResponse"
            }
          },
          "next": {
            "type": "string",
            "description": "Pass as `before` for the next page; null on the last page.",
            "format": "uuid",
            "nullable": true
          }
        }
      },
      "WebhookDeliveryResponse": {
        "required": [
          "id",
          "eventId",
          "type",
          "emailId",
          "to",
          "status",
          "attempts",
          "lastAttemptAt",
          "lastStatusCode",
          "lastError",
          "lastDurationMs",
          "responseSnippet",
          "nextAttemptAt",
          "createdAt"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "eventId": {
            "type": "string",
            "description": "The webhook-id header: the same for every attempt.",
            "format": "uuid"
          },
          "type": {
            "type": "string",
            "description": "The payload type, e.g. email.delivered.v1."
          },
          "emailId": {
            "type": "string",
            "format": "uuid",
            "nullable": true
          },
          "to": {
            "type": "string",
            "nullable": true
          },
          "status": {
            "type": "string",
            "description": "`pending`, `delivered` or `failed`."
          },
          "attempts": {
            "type": "integer",
            "format": "int32"
          },
          "lastAttemptAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "lastStatusCode": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "lastError": {
            "type": "string",
            "nullable": true
          },
          "lastDurationMs": {
            "type": "integer",
            "format": "int32",
            "nullable": true
          },
          "responseSnippet": {
            "type": "string",
            "description": "The start of the last response body, at most 256 characters.",
            "nullable": true
          },
          "nextAttemptAt": {
            "type": "string",
            "description": "Set while pending.",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "WebhookEndpointList": {
        "required": [
          "data"
        ],
        "type": "object",
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEndpointResponse"
            }
          }
        }
      },
      "WebhookEndpointResponse": {
        "required": [
          "id",
          "url",
          "description",
          "events",
          "mode",
          "paused",
          "pauseReason",
          "pausedAt",
          "failingSince",
          "secretHint",
          "previousSecretExpiresAt",
          "createdAt",
          "lastDelivery"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "url": {
            "type": "string"
          },
          "description": {
            "type": "string",
            "nullable": true
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The message events it gets: accepted, queued, delivered, deferred, bounced, expired, complained, suppressed."
          },
          "mode": {
            "type": "string",
            "description": "`live`, or `test`: a test endpoint gets the events of test-key messages only."
          },
          "paused": {
            "type": "boolean"
          },
          "pauseReason": {
            "type": "string",
            "nullable": true
          },
          "pausedAt": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "failingSince": {
            "type": "string",
            "description": "Every attempt since then failed; after 72 hours of that the endpoint is paused.",
            "format": "date-time",
            "nullable": true
          },
          "secretHint": {
            "type": "string",
            "description": "The last characters of the signing secret."
          },
          "previousSecretExpiresAt": {
            "type": "string",
            "description": "Until then the secret from before the last rotation still signs as well.",
            "format": "date-time",
            "nullable": true
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "lastDelivery": {
            "oneOf": [
              {
                "enum": [
                  null
                ],
                "nullable": true
              },
              {
                "$ref": "#/components/schemas/WebhookLastDelivery"
              }
            ]
          }
        }
      },
      "WebhookLastDelivery": {
        "required": [
          "at",
          "statusCode",
          "status"
        ],
        "type": "object",
        "properties": {
          "at": {
            "type": "string",
            "description": "When the attempt was made.",
            "format": "date-time"
          },
          "statusCode": {
            "type": "integer",
            "description": "The receiver's HTTP status; null when no answer came (timeout, refused, blocked address).",
            "format": "int32",
            "nullable": true
          },
          "status": {
            "type": "string",
            "description": "The delivery's status now: `pending` (to be retried), `delivered` or `failed`."
          }
        }
      }
    },
    "securitySchemes": {
      "Bearer": {
        "type": "http",
        "description": "An API key: pk_live_… or pk_test_…",
        "scheme": "bearer"
      }
    }
  },
  "security": [
    {
      "Bearer": [ ]
    }
  ],
  "tags": [
    {
      "name": "Emails"
    },
    {
      "name": "Domains"
    },
    {
      "name": "Suppressions"
    },
    {
      "name": "Webhooks"
    }
  ]
}