{
  "openapi": "3.1.0",
  "info": {
    "title": "PennyPost API",
    "version": "1.0.0",
    "description": "Canonical contract for customer-facing Transactional Emails, Marketing Emails, suppressions, webhooks, account introspection, and API-key management. Bearer auth uses pp_live_/pp_test_ keys unless an operation explicitly requires a pp_sess_ dashboard session. Test keys simulate delivery and mark rows mode:test. Browser signup, OAuth, domain setup, and general billing UI routes are documented separately because they are dashboard workflows, not SDK operations.",
    "contact": {
      "email": "hello@pennypost.io",
      "url": "https://pennypost.io/docs"
    }
  },
  "servers": [
    {
      "url": "https://api.pennypost.io"
    }
  ],
  "security": [
    {
      "apiKey": []
    }
  ],
  "components": {
    "securitySchemes": {
      "apiKey": {
        "type": "http",
        "scheme": "bearer",
        "description": "pp_live_… or pp_test_… API key"
      },
      "session": {
        "type": "http",
        "scheme": "bearer",
        "description": "pp_sess_… dashboard session token; management and billing only, never email sending"
      },
      "mailgunBasic": {
        "type": "http",
        "scheme": "basic",
        "description": "Ghost/Mailgun compatibility only: username api, password a pp_live_ API key"
      }
    },
    "schemas": {
      "SendEmailRequest": {
        "type": "object",
        "required": [
          "from",
          "to",
          "subject"
        ],
        "properties": {
          "from": {
            "type": "string",
            "description": "Display name optional: 'Receipts <receipts@yourdomain.com>'. Domain must be verified for live keys."
          },
          "to": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "minItems": 1,
            "maxItems": 50,
            "description": "Always an array."
          },
          "subject": {
            "type": "string",
            "maxLength": 998
          },
          "html": {
            "type": "string",
            "maxLength": 2000000
          },
          "text": {
            "type": "string",
            "maxLength": 2000000
          },
          "reply_to": {
            "type": "string"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 64
            },
            "maxItems": 10
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "maxLength": 512
            }
          },
          "headers": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "maxLength": 998
            }
          },
          "cc": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 50,
            "description": "Carbon-copy recipients. to, cc, and bcc together allow at most 50."
          },
          "bcc": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 50,
            "description": "Blind-carbon-copy recipients."
          },
          "scheduled_at": {
            "type": "string",
            "description": "ISO 8601 time to send later (future, within one year). The response is a scheduled email you can reschedule (PATCH) or cancel (DELETE) by its id."
          },
          "attachments": {
            "type": "array",
            "description": "Attachments and inline images. content is base64. A content_id makes it inline (reference it in html as cid:that-id). Total under about 7 MB.",
            "items": {
              "type": "object",
              "required": [
                "filename",
                "content"
              ],
              "properties": {
                "filename": {
                  "type": "string"
                },
                "content": {
                  "type": "string",
                  "description": "Base64-encoded bytes."
                },
                "content_type": {
                  "type": "string"
                },
                "content_id": {
                  "type": "string",
                  "description": "Present = inline image."
                }
              }
            }
          }
        },
        "description": "At least one of html or text is required."
      },
      "Recipient": {
        "type": "object",
        "required": [
          "to"
        ],
        "properties": {
          "to": {
            "type": "string"
          },
          "id": {
            "type": "string"
          },
          "reason": {
            "type": "string"
          }
        }
      },
      "SendEmailResponse": {
        "type": "object",
        "required": [
          "accepted",
          "suppressed"
        ],
        "properties": {
          "accepted": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Recipient"
            }
          },
          "suppressed": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Recipient"
            },
            "description": "On the suppression list: reported, not sent, not charged."
          },
          "quarantined": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Recipient"
            },
            "description": "Present only while the account is restricted: sends to never-delivered recipients are held."
          },
          "failed": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Recipient"
            },
            "description": "Provider failures; safe to retry."
          }
        }
      },
      "EmailEvent": {
        "type": "object",
        "required": [
          "type",
          "at"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "email.sent",
              "email.delivered",
              "email.bounced",
              "email.complained",
              "email.failed"
            ]
          },
          "code": {
            "type": [
              "string",
              "null"
            ]
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Email": {
        "type": "object",
        "required": [
          "id",
          "from",
          "to",
          "subject",
          "status",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "em_… ULID"
          },
          "from": {
            "type": "string"
          },
          "to": {
            "type": "string"
          },
          "subject": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "accepted",
              "sent",
              "delivered",
              "bounced",
              "complained",
              "failed"
            ],
            "description": "Terminal statuses win: a late delivery event never un-bounces."
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "metadata": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EmailEvent"
            },
            "description": "Present on GET /v1/emails/{id} only."
          },
          "key_id": {
            "type": [
              "string",
              "null"
            ],
            "description": "API key that created this email. Null on broadcast sends and rows from before 2026-08-19."
          }
        }
      },
      "EmailPage": {
        "type": "object",
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Email"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Suppression": {
        "type": "object",
        "required": [
          "email",
          "reason"
        ],
        "properties": {
          "email": {
            "type": "string"
          },
          "reason": {
            "type": "string",
            "enum": [
              "bounce",
              "complaint",
              "manual"
            ]
          },
          "source_email_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SuppressionPage": {
        "type": "object",
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Suppression"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "type",
              "code",
              "message"
            ],
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "invalid_request",
                  "authentication",
                  "rate_limit",
                  "provider"
                ]
              },
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              },
              "param": {
                "type": "string"
              },
              "doc": {
                "type": "string"
              },
              "retryable": {
                "type": "boolean"
              },
              "detail": {}
            }
          }
        }
      },
      "WebhookEndpoint": {
        "type": "object",
        "required": [
          "id",
          "url",
          "events",
          "status",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "email.sent",
                "email.delivered",
                "email.delivery_delayed",
                "email.bounced",
                "email.complained",
                "email.failed",
                "email.opened",
                "email.clicked",
                "contact.unsubscribed",
                "suppression.added",
                "suppression.removed"
              ]
            }
          },
          "secret": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "consecutive_failures": {
            "type": "integer"
          },
          "last_success_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "last_failure_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          }
        }
      },
      "WebhookList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookEndpoint"
            }
          }
        }
      },
      "WebhookTestResult": {
        "type": "object",
        "required": [
          "delivered",
          "endpoint_status",
          "type"
        ],
        "properties": {
          "delivered": {
            "type": "boolean"
          },
          "endpoint_status": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "email.sent",
              "email.delivered",
              "email.delivery_delayed",
              "email.bounced",
              "email.complained",
              "email.failed",
              "email.opened",
              "email.clicked",
              "contact.unsubscribed",
              "suppression.added",
              "suppression.removed"
            ]
          }
        }
      },
      "CreateWebhookRequest": {
        "type": "object",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "email.sent",
                "email.delivered",
                "email.delivery_delayed",
                "email.bounced",
                "email.complained",
                "email.failed",
                "email.opened",
                "email.clicked",
                "contact.unsubscribed",
                "suppression.added",
                "suppression.removed"
              ]
            }
          }
        }
      },
      "Account": {
        "type": "object",
        "required": [
          "id",
          "plan",
          "status",
          "daily_cap",
          "enforcement"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "contact_email": {
            "type": [
              "string",
              "null"
            ]
          },
          "plan": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "daily_cap": {
            "type": "integer"
          },
          "month_to_date_sent": {
            "type": "integer"
          },
          "card_on_file": {
            "type": "boolean"
          },
          "enforcement": {
            "type": "object",
            "properties": {
              "state": {
                "type": "string"
              },
              "reason": {
                "type": "string"
              }
            }
          },
          "first_send_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Set once, on the first send ever (test or live). Powers setup checklists; never resets."
          },
          "first_live_send_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Set once, on the first live-mode send. Never resets."
          },
          "created_at": {
            "type": "string"
          }
        }
      },
      "CreateKeyRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "scope": {
            "type": "string",
            "enum": [
              "full",
              "send"
            ],
            "description": "full (default) manages everything; send can only call the send endpoints."
          },
          "domain": {
            "type": "string",
            "description": "Restrict SEND operations (transactional and marketing from-addresses) to one domain; reads stay account-wide."
          },
          "permissions": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "emails.send",
                "emails.read",
                "marketing.edit",
                "marketing.read",
                "suppressions.edit",
                "suppressions.read",
                "webhooks.edit",
                "webhooks.read",
                "logs.read",
                "keys.edit",
                "account.read"
              ]
            },
            "description": "Granular grants; wins over the legacy scope shorthand. Omitted with scope full (or nothing) = every permission."
          },
          "expires_at": {
            "type": "string",
            "description": "Future ISO timestamp; the key 401s key_expired past it. Omit for no expiry."
          }
        }
      },
      "ApiKeySummary": {
        "type": "object",
        "required": [
          "created_at",
          "id",
          "mode",
          "permissions",
          "prefix",
          "scope"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "prefix": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string"
          },
          "scope": {
            "type": "string",
            "enum": [
              "full",
              "send"
            ]
          },
          "domain": {
            "type": [
              "string",
              "null"
            ]
          },
          "permissions": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "emails.send",
                "emails.read",
                "marketing.edit",
                "marketing.read",
                "suppressions.edit",
                "suppressions.read",
                "webhooks.edit",
                "webhooks.read",
                "logs.read",
                "keys.edit",
                "account.read"
              ]
            }
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ApiKeyList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ApiKeySummary"
            }
          }
        }
      },
      "SignupCapacity": {
        "type": "object",
        "properties": {
          "signup_open": {
            "type": "boolean"
          },
          "mode": {
            "type": "string"
          }
        }
      },
      "Audience": {
        "type": "object",
        "required": [
          "id",
          "name",
          "contact_count",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "contact_count": {
            "type": "integer"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AudienceList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Audience"
            }
          }
        }
      },
      "Contact": {
        "type": "object",
        "required": [
          "email",
          "status"
        ],
        "properties": {
          "email": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "unsubscribed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "properties": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "ContactPage": {
        "type": "object",
        "required": [
          "data",
          "has_more"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Contact"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ContactInput": {
        "type": "object",
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "properties": {
            "type": "object",
            "additionalProperties": {
              "type": "string",
              "maxLength": 200
            },
            "maxProperties": 20,
            "description": "Custom fields. Usable as {{key}} template vars and in broadcast filters."
          }
        }
      },
      "AddContactsRequest": {
        "type": "object",
        "required": [
          "contacts"
        ],
        "properties": {
          "contacts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ContactInput"
            }
          }
        }
      },
      "AddContactsResult": {
        "type": "object",
        "required": [
          "added",
          "duplicates"
        ],
        "properties": {
          "added": {
            "type": "integer"
          },
          "duplicates": {
            "type": "integer"
          },
          "rejected": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "UpdateContactRequest": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string"
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "properties": {
            "type": [
              "object",
              "null"
            ],
            "additionalProperties": {
              "type": "string",
              "maxLength": 200
            },
            "maxProperties": 20,
            "description": "Replaces the whole map when present; null clears it."
          }
        }
      },
      "CreateAudienceRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string"
          }
        }
      },
      "BroadcastCounters": {
        "type": "object",
        "properties": {
          "queued": {
            "type": "integer"
          },
          "sent": {
            "type": "integer"
          },
          "delivered": {
            "type": "integer"
          },
          "bounced": {
            "type": "integer"
          },
          "complained": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          },
          "skipped": {
            "type": "integer"
          },
          "unsubscribed": {
            "type": "integer"
          },
          "opened": {
            "type": "integer"
          },
          "clicked": {
            "type": "integer"
          }
        }
      },
      "Broadcast": {
        "type": "object",
        "required": [
          "id",
          "audience_id",
          "from",
          "subject",
          "status",
          "counters",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "audience_id": {
            "type": "string"
          },
          "from": {
            "type": "string"
          },
          "subject": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "counters": {
            "$ref": "#/components/schemas/BroadcastCounters"
          },
          "scheduled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "started_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "filter": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BroadcastFilter"
            }
          },
          "format": {
            "type": "string",
            "enum": [
              "html",
              "markdown"
            ],
            "description": "How the body was composed. Bodies (html/text/source) ride only on single-broadcast responses, never lists."
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "description": "Markdown source when format is markdown; round-trips into editors."
          },
          "html": {
            "type": [
              "string",
              "null"
            ]
          },
          "text": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "BroadcastList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Broadcast"
            }
          }
        }
      },
      "CreateBroadcastRequest": {
        "type": "object",
        "required": [
          "audience_id",
          "from",
          "subject"
        ],
        "properties": {
          "audience_id": {
            "type": "string"
          },
          "from": {
            "type": "string",
            "description": "Display name optional: 'Weekly Digest <news@yourdomain.com>'. Domain must be verified for live keys."
          },
          "subject": {
            "type": "string"
          },
          "html": {
            "type": [
              "string",
              "null"
            ]
          },
          "text": {
            "type": [
              "string",
              "null"
            ]
          },
          "reply_to": {
            "type": [
              "string",
              "null"
            ]
          },
          "scheduled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "filter": {
            "type": "array",
            "maxItems": 5,
            "items": {
              "$ref": "#/components/schemas/BroadcastFilter"
            },
            "description": "Send only to contacts matching every condition, evaluated at send time."
          },
          "format": {
            "type": "string",
            "enum": [
              "html",
              "markdown"
            ]
          },
          "source": {
            "type": "string",
            "maxLength": 200000,
            "description": "Markdown body, rendered server-side at save. Required when format is markdown; html/text are then ignored."
          }
        }
      },
      "SendBroadcastRequest": {
        "type": "object",
        "properties": {
          "confirm_opt_in": {
            "type": "boolean"
          }
        }
      },
      "RemoveResult": {
        "type": "object",
        "required": [
          "removed"
        ],
        "properties": {
          "removed": {
            "type": "boolean"
          }
        }
      },
      "UpdateBroadcastRequest": {
        "type": "object",
        "properties": {
          "audience_id": {
            "type": "string"
          },
          "from": {
            "type": "string",
            "description": "Display name optional: 'Weekly Digest <news@yourdomain.com>'. Domain must be verified for live keys."
          },
          "subject": {
            "type": "string"
          },
          "html": {
            "type": [
              "string",
              "null"
            ]
          },
          "text": {
            "type": [
              "string",
              "null"
            ]
          },
          "reply_to": {
            "type": [
              "string",
              "null"
            ]
          },
          "scheduled_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "filter": {
            "type": [
              "array",
              "null"
            ],
            "maxItems": 5,
            "items": {
              "$ref": "#/components/schemas/BroadcastFilter"
            }
          },
          "format": {
            "type": "string",
            "enum": [
              "html",
              "markdown"
            ]
          },
          "source": {
            "type": "string",
            "maxLength": 200000
          }
        }
      },
      "SendEmailBatchResponse": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "description": "One entry per batch item, in order. Each entry is exactly what POST /v1/emails returns for that item.",
            "items": {
              "$ref": "#/components/schemas/SendEmailResponse"
            }
          }
        }
      },
      "BroadcastFilter": {
        "type": "object",
        "required": [
          "property",
          "equals"
        ],
        "description": "One send-time condition. Conditions in a filter AND together against contact properties.",
        "properties": {
          "property": {
            "type": "string",
            "maxLength": 40
          },
          "equals": {
            "type": "string",
            "maxLength": 200
          }
        }
      },
      "TestBroadcastRequest": {
        "type": "object",
        "required": [
          "to"
        ],
        "properties": {
          "to": {
            "type": "array",
            "minItems": 1,
            "maxItems": 5,
            "items": {
              "type": "string"
            },
            "description": "Where to deliver the rendered draft. Subject is prefixed [Test]; counters and billing are untouched."
          }
        }
      },
      "TestBroadcastResult": {
        "type": "object",
        "required": [
          "sent",
          "to"
        ],
        "properties": {
          "sent": {
            "type": "integer"
          },
          "to": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "TestWebhookRequest": {
        "type": "object",
        "description": "Optional: which subscribed event type to sample. Defaults to the endpoint's first subscribed event. The sample is production-shaped (mode test, recipient webhook-test@example.com); there is no synthetic test envelope.",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "email.sent",
              "email.delivered",
              "email.delivery_delayed",
              "email.bounced",
              "email.complained",
              "email.failed",
              "email.opened",
              "email.clicked",
              "contact.unsubscribed",
              "suppression.added",
              "suppression.removed"
            ]
          }
        }
      },
      "UpdateWebhookRequest": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "email.sent",
                "email.delivered",
                "email.delivery_delayed",
                "email.bounced",
                "email.complained",
                "email.failed",
                "email.opened",
                "email.clicked",
                "contact.unsubscribed",
                "suppression.added",
                "suppression.removed"
              ]
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          }
        }
      },
      "WebhookUpdateResult": {
        "type": "object",
        "required": [
          "id",
          "url",
          "events",
          "status",
          "consecutive_failures"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "events": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "email.sent",
                "email.delivered",
                "email.delivery_delayed",
                "email.bounced",
                "email.complained",
                "email.failed",
                "email.opened",
                "email.clicked",
                "contact.unsubscribed",
                "suppression.added",
                "suppression.removed"
              ]
            }
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "disabled"
            ]
          },
          "consecutive_failures": {
            "type": "integer"
          }
        }
      },
      "WebhookSecret": {
        "type": "object",
        "required": [
          "id",
          "secret"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "secret": {
            "type": "string"
          }
        }
      },
      "WebhookDelivery": {
        "type": "object",
        "required": [
          "id",
          "event_id",
          "type",
          "attempt",
          "ok",
          "status",
          "url",
          "body",
          "at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "event_id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "email.sent",
              "email.delivered",
              "email.delivery_delayed",
              "email.bounced",
              "email.complained",
              "email.failed",
              "email.opened",
              "email.clicked",
              "contact.unsubscribed",
              "suppression.added",
              "suppression.removed"
            ]
          },
          "attempt": {
            "type": "integer"
          },
          "ok": {
            "type": "boolean"
          },
          "status": {
            "type": "integer",
            "description": "Receiver's HTTP status; 0 = unreachable."
          },
          "url": {
            "type": "string"
          },
          "body": {
            "type": "string",
            "description": "The exact JSON body sent (what the signature covers)."
          },
          "at": {
            "type": "string"
          }
        }
      },
      "WebhookDeliveryList": {
        "type": "object",
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/WebhookDelivery"
            }
          }
        }
      },
      "ReplayResult": {
        "type": "object",
        "required": [
          "delivered",
          "event_id",
          "type"
        ],
        "properties": {
          "delivered": {
            "type": "boolean"
          },
          "event_id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "email.sent",
              "email.delivered",
              "email.delivery_delayed",
              "email.bounced",
              "email.complained",
              "email.failed",
              "email.opened",
              "email.clicked",
              "contact.unsubscribed",
              "suppression.added",
              "suppression.removed"
            ]
          }
        }
      },
      "RequestLogEntry": {
        "type": "object",
        "required": [
          "id",
          "method",
          "path",
          "status",
          "ms",
          "at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "method": {
            "type": "string"
          },
          "path": {
            "type": "string"
          },
          "status": {
            "type": "integer"
          },
          "ms": {
            "type": "integer"
          },
          "credential": {
            "type": [
              "string",
              "null"
            ],
            "description": "The API key prefix that made the call."
          },
          "at": {
            "type": "string"
          }
        }
      },
      "RequestLogPage": {
        "type": "object",
        "required": [
          "data",
          "has_more"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RequestLogEntry"
            }
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "CreateKeyResponse": {
        "type": "object",
        "required": [
          "created_at",
          "id",
          "key",
          "mode",
          "name",
          "permissions",
          "scope"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "enum": [
              "live",
              "test"
            ]
          },
          "scope": {
            "type": "string",
            "enum": [
              "full",
              "send"
            ]
          },
          "domain": {
            "type": [
              "string",
              "null"
            ]
          },
          "key": {
            "type": "string",
            "description": "The raw key. Appears exactly once, here."
          },
          "created_at": {
            "type": "string"
          },
          "permissions": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "emails.send",
                "emails.read",
                "marketing.edit",
                "marketing.read",
                "suppressions.edit",
                "suppressions.read",
                "webhooks.edit",
                "webhooks.read",
                "logs.read",
                "keys.edit",
                "account.read"
              ]
            }
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "UpdateKeyRequest": {
        "type": "object",
        "description": "Edit a key in place. Mode is immutable (baked into the key string). Setting scope full clears any domain restriction. domain null clears the restriction.",
        "properties": {
          "name": {
            "type": "string"
          },
          "scope": {
            "type": "string",
            "enum": [
              "full",
              "send"
            ]
          },
          "domain": {
            "type": [
              "string",
              "null"
            ]
          },
          "permissions": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": [
                "emails.send",
                "emails.read",
                "marketing.edit",
                "marketing.read",
                "suppressions.edit",
                "suppressions.read",
                "webhooks.edit",
                "webhooks.read",
                "logs.read",
                "keys.edit",
                "account.read"
              ]
            }
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "description": "Future ISO timestamp; null clears the expiry."
          }
        }
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "schema": {
          "type": "string"
        },
        "description": "Same key returns the same result instead of re-sending."
      }
    }
  },
  "paths": {
    "/v1/audiences": {
      "get": {
        "operationId": "listAudiences",
        "summary": "List Marketing Emails audiences",
        "responses": {
          "200": {
            "description": "Audience list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AudienceList"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createAudience",
        "summary": "Create an audience",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAudienceRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Audience created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Audience"
                }
              }
            }
          }
        }
      }
    },
    "/v1/audiences/{id}": {
      "get": {
        "operationId": "getAudience",
        "summary": "Get an audience",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Audience",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Audience"
                }
              }
            }
          },
          "404": {
            "description": "Not found"
          }
        }
      },
      "delete": {
        "operationId": "deleteAudience",
        "summary": "Delete an audience and its contacts",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RemoveResult"
                }
              }
            }
          }
        }
      }
    },
    "/v1/audiences/{id}/contacts": {
      "get": {
        "operationId": "listAudienceContacts",
        "summary": "List contacts",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "subscribed",
                "unsubscribed"
              ]
            }
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Contact page",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactPage"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "addAudienceContacts",
        "summary": "Add up to 1,000 contacts without resurrecting opt-outs",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddContactsRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Import result"
          },
          "200": {
            "description": "AddContactsResult",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddContactsResult"
                }
              }
            }
          }
        }
      }
    },
    "/v1/audiences/{id}/contacts/{email}": {
      "delete": {
        "operationId": "deleteAudienceContact",
        "summary": "Delete one contact",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "email"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deleted",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RemoveResult"
                }
              }
            }
          },
          "404": {
            "description": "Contact not found"
          }
        }
      },
      "patch": {
        "operationId": "updateAudienceContact",
        "summary": "Explicitly update or resubscribe one contact",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "email",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "format": "email"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateContactRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated contact",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Contact"
                }
              }
            }
          },
          "404": {
            "description": "Contact not found"
          }
        }
      }
    },
    "/v1/broadcasts": {
      "get": {
        "operationId": "listBroadcasts",
        "summary": "List Marketing Emails",
        "responses": {
          "200": {
            "description": "Broadcast list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BroadcastList"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createBroadcast",
        "summary": "Create a Marketing Email draft",
        "description": "Provide html or text. Their combined length must be at most 200,000 characters. scheduled_at must be in the future and no more than one year away.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBroadcastRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Draft created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Broadcast"
                }
              }
            }
          }
        }
      }
    },
    "/v1/broadcasts/{id}": {
      "get": {
        "operationId": "getBroadcast",
        "summary": "Get status and counters",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Broadcast",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Broadcast"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateBroadcast",
        "summary": "Edit a draft marketing email",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateBroadcastRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated draft",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Broadcast"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      },
      "delete": {
        "operationId": "deleteBroadcast",
        "summary": "Delete a draft marketing email",
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RemoveResult"
                }
              }
            }
          }
        },
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ]
      }
    },
    "/v1/broadcasts/{id}/send": {
      "post": {
        "operationId": "sendBroadcast",
        "summary": "Send or schedule a draft",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendBroadcastRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Queued",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Broadcast"
                }
              }
            }
          }
        }
      }
    },
    "/v1/broadcasts/{id}/cancel": {
      "post": {
        "operationId": "cancelBroadcast",
        "summary": "Cancel at the next chunk boundary",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Canceled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Broadcast"
                }
              }
            }
          }
        }
      }
    },
    "/v1/billing/marketing": {
      "get": {
        "operationId": "getMarketingBilling",
        "summary": "Marketing contact plan and usage",
        "security": [
          {
            "session": []
          }
        ],
        "responses": {
          "200": {
            "description": "Plan, current subscribed contact count, contact limit, subscription id, and cancellation state"
          }
        }
      }
    },
    "/v1/billing/marketing/subscribe": {
      "post": {
        "operationId": "subscribeMarketing",
        "summary": "Start a separate Marketing Emails subscription",
        "security": [
          {
            "session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "plan"
                ],
                "properties": {
                  "plan": {
                    "type": "string",
                    "enum": [
                      "marketing5000",
                      "marketing10000",
                      "marketing25000",
                      "marketing50000",
                      "marketing100000",
                      "marketing150000"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Subscription created"
          }
        }
      }
    },
    "/v1/billing/marketing/change": {
      "post": {
        "operationId": "changeMarketingPlan",
        "summary": "Change the Marketing Emails contact tier",
        "security": [
          {
            "session": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "plan"
                ],
                "properties": {
                  "plan": {
                    "type": "string",
                    "enum": [
                      "marketing5000",
                      "marketing10000",
                      "marketing25000",
                      "marketing50000",
                      "marketing100000",
                      "marketing150000"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Plan changed"
          }
        }
      }
    },
    "/v1/billing/marketing/cancel": {
      "post": {
        "operationId": "cancelMarketing",
        "summary": "Cancel Marketing Emails at period end",
        "security": [
          {
            "session": []
          }
        ],
        "responses": {
          "200": {
            "description": "Cancellation scheduled"
          }
        }
      }
    },
    "/v3/{domain}/messages": {
      "post": {
        "operationId": "sendGhostNewsletter",
        "summary": "Mailgun-compatible Ghost delivery endpoint",
        "security": [
          {
            "mailgunBasic": []
          }
        ],
        "parameters": [
          {
            "name": "domain",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Queued"
          },
          "501": {
            "description": "Unsupported Mailgun surface"
          }
        }
      }
    },
    "/v1/emails": {
      "post": {
        "operationId": "sendEmail",
        "summary": "Send one email to up to 50 recipients",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendEmailRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Per-recipient outcome",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendEmailResponse"
                }
              }
            }
          },
          "401": {
            "description": "missing_api_key / invalid_api_key / account_paused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "validation_failed / domain_not_verified",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "daily_cap_reached (retryable)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "send_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "description": "One request is ONE message: everyone on the to line sees each other, cc and bcc work like a normal mail client, and all recipients share one provider message. To send each person their own copy, use POST /v1/emails/batch. Each recipient still gets its own log row, and suppressed recipients are removed from the envelope and reported."
      },
      "get": {
        "operationId": "listEmails",
        "summary": "List emails, most-recent first",
        "parameters": [
          {
            "name": "to",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Exact recipient filter"
          },
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "domain",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Scope to one verified sending domain (subdomains roll up)."
          },
          {
            "name": "key",
            "in": "query",
            "schema": {
              "type": "string"
            },
            "description": "Scope to sends created by one API key id (key_…)."
          }
        ],
        "responses": {
          "200": {
            "description": "Page of emails (30-day retention)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailPage"
                }
              }
            }
          },
          "422": {
            "description": "filter_not_supported for status/tag/time filters",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/emails/{id}": {
      "get": {
        "operationId": "getEmail",
        "summary": "One email with its event timeline",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Email + events",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Email"
                }
              }
            }
          },
          "404": {
            "description": "email_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "rescheduleEmail",
        "summary": "Reschedule a scheduled (not yet sent) email",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "scheduled_at"
                ],
                "properties": {
                  "scheduled_at": {
                    "type": "string",
                    "description": "New ISO 8601 send time."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The updated scheduled email."
          },
          "404": {
            "description": "No scheduled email with that id."
          }
        }
      },
      "delete": {
        "operationId": "cancelEmail",
        "summary": "Cancel a scheduled (not yet sent) email",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The canceled scheduled email."
          },
          "404": {
            "description": "No scheduled email with that id."
          }
        }
      }
    },
    "/v1/suppressions": {
      "get": {
        "operationId": "listSuppressions",
        "summary": "List suppressions",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 20
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Page",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuppressionPage"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "addSuppression",
        "summary": "Manually suppress an address",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Suppression"
                }
              }
            }
          }
        }
      }
    },
    "/v1/suppressions/{email}": {
      "delete": {
        "operationId": "removeSuppression",
        "summary": "Remove a suppression",
        "parameters": [
          {
            "name": "email",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "removed": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "suppression_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "Complaint suppressions can't be removed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks": {
      "get": {
        "operationId": "listWebhooks",
        "summary": "List webhook endpoints",
        "responses": {
          "200": {
            "description": "List",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookList"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createWebhook",
        "summary": "Create a webhook endpoint",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhookRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookEndpoint"
                }
              }
            }
          },
          "403": {
            "description": "Endpoint limit reached.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid url or events.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}": {
      "delete": {
        "operationId": "deleteWebhook",
        "summary": "Delete a webhook endpoint",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Removed",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "removed": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateWebhook",
        "summary": "Update an endpoint: url, events, or status (pause/resume)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWebhookRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookUpdateResult"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid url, events, or status.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}/test": {
      "post": {
        "operationId": "testWebhook",
        "summary": "Send a signed, production-shaped sample event to one endpoint",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookTestResult"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "type is not one of this endpoint's subscribed events.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TestWebhookRequest"
              }
            }
          }
        }
      }
    },
    "/v1/account": {
      "get": {
        "operationId": "getAccount",
        "summary": "Account status, plan, and enforcement state",
        "responses": {
          "200": {
            "description": "Account",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          }
        }
      }
    },
    "/v1/keys": {
      "get": {
        "operationId": "listKeys",
        "summary": "List API keys",
        "responses": {
          "200": {
            "description": "Keys",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyList"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "createKey",
        "summary": "Create an API key (the raw key appears once, in this response)",
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateKeyRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateKeyResponse"
                }
              }
            }
          },
          "422": {
            "description": "Live keys need a verified domain.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/keys/{id}": {
      "delete": {
        "operationId": "revokeKey",
        "summary": "Revoke an API key",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Revoked",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "revoked": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "patch": {
        "operationId": "updateKey",
        "summary": "Edit a key's name, scope, or domain restriction in place",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateKeyRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeySummary"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Invalid scope or domain.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/signup-status": {
      "get": {
        "operationId": "getPublicCapacity",
        "summary": "Check whether new free-account signup is open or waitlisted",
        "security": [],
        "responses": {
          "200": {
            "description": "Current public admission mode",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SignupCapacity"
                }
              }
            }
          }
        }
      }
    },
    "/v1/emails/batch": {
      "post": {
        "operationId": "sendEmailBatch",
        "summary": "Send up to 100 emails in one request",
        "description": "The body is a raw JSON array of send objects. Validation is all-or-nothing: any invalid item rejects the whole batch, and the error names the item index. One Idempotency-Key covers the whole batch. Daily and monthly limits count the total recipients across all items, so a batch either clears them whole or returns 429 before anything sends.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "minItems": 1,
                "maxItems": 100,
                "items": {
                  "$ref": "#/components/schemas/SendEmailRequest"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Per-item outcome, in order",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendEmailBatchResponse"
                }
              }
            }
          },
          "401": {
            "description": "missing_api_key / invalid_api_key / account_paused",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "validation_failed / domain_not_verified",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "daily_cap_reached (retryable)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "send_failed: no item could be delivered",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/broadcasts/{id}/test": {
      "post": {
        "operationId": "testBroadcast",
        "summary": "Send a test copy of a marketing email",
        "description": "Delivers the rendered draft to up to 5 addresses with a [Test] subject prefix. Personalization runs with an empty contact, so {{name}} resolves blank and property vars stay literal. Never touches counters, claims, or billing.",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TestBroadcastRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Delivered",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestBroadcastResult"
                }
              }
            }
          },
          "404": {
            "description": "broadcast_not_found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "validation_failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "rate_limited (retryable)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}/rotate": {
      "post": {
        "operationId": "rotateWebhookSecret",
        "summary": "Issue a new signing secret, effective on the next delivery",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "New secret",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookSecret"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}/deliveries": {
      "get": {
        "operationId": "listWebhookDeliveries",
        "summary": "Delivery attempts for one endpoint, newest first (30-day retention)",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Deliveries",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookDeliveryList"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/webhooks/{id}/deliveries/{deliveryId}/replay": {
      "post": {
        "operationId": "replayWebhookDelivery",
        "summary": "Re-send a recorded delivery body with a fresh signature",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "deliveryId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Replayed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReplayResult"
                }
              }
            }
          },
          "404": {
            "description": "Not found.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "Endpoint is paused; resume it first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/logs": {
      "get": {
        "operationId": "listLogs",
        "summary": "Every API-key request on the account, newest first (30-day retention, no bodies)",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RequestLogPage"
                }
              }
            }
          }
        }
      }
    },
    "/v1/emails/metrics": {
      "get": {
        "operationId": "getEmailMetrics",
        "summary": "Account send metrics over a date range",
        "security": [
          {
            "apiKey": []
          }
        ],
        "parameters": [
          {
            "name": "start",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Start day YYYY-MM-DD (default 30 days ago)."
          },
          {
            "name": "end",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "End day YYYY-MM-DD (default today). Range at most 92 days."
          }
        ],
        "responses": {
          "200": {
            "description": "Totals and rates summed from the daily rollups.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "start": {
                      "type": "string"
                    },
                    "end": {
                      "type": "string"
                    },
                    "metrics": {
                      "type": "object",
                      "properties": {
                        "sent": {
                          "type": "integer"
                        },
                        "delivered": {
                          "type": "integer"
                        },
                        "bounced": {
                          "type": "integer"
                        },
                        "complained": {
                          "type": "integer"
                        }
                      }
                    },
                    "rates": {
                      "type": "object",
                      "properties": {
                        "delivered": {
                          "type": "number"
                        },
                        "bounced": {
                          "type": "number"
                        },
                        "complained": {
                          "type": "number"
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/suppressions/batch": {
      "post": {
        "operationId": "addSuppressionsBatch",
        "summary": "Add up to 100 addresses to the suppression list",
        "security": [
          {
            "apiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "emails"
                ],
                "properties": {
                  "emails": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "1 to 100 addresses."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "How many were added, plus any invalid addresses.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "added": {
                      "type": "integer"
                    },
                    "invalid": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      },
      "delete": {
        "operationId": "removeSuppressionsBatch",
        "summary": "Remove up to 100 addresses from the suppression list",
        "security": [
          {
            "apiKey": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "emails"
                ],
                "properties": {
                  "emails": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "How many were removed, plus complaint suppressions that are locked.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "removed": {
                      "type": "integer"
                    },
                    "locked": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
