{
  "components": {
    "parameters": {},
    "schemas": {
      "AddDomainBody": {
        "description": "Body for POST /api/domains. `projectId` is optional for API-key auth (derived from key) and required for session auth. `region` pins the SES region; on the first domain it locks the project, after that it must match the project's region. `stream` assigns the identity to transactional or marketing traffic at creation; omit it to leave the identity serving both.",
        "properties": {
          "domain": {
            "maxLength": 253,
            "minLength": 3,
            "type": "string"
          },
          "projectId": {
            "format": "uuid",
            "type": "string"
          },
          "region": {
            "description": "Override SES region for this domain. Defaults to the project region or the env default. Required to match an existing project region.",
            "enum": [
              "us-east-1",
              "us-west-2",
              "eu-west-1"
            ],
            "type": "string"
          },
          "stream": {
            "$ref": "#/components/schemas/SendingStream"
          },
          "streamDefault": {
            "description": "Make this the project's default identity for `stream`. Requires `stream`. Setting it demotes whichever identity held it.",
            "type": "boolean"
          }
        },
        "required": [
          "domain"
        ],
        "type": "object"
      },
      "AddSuppression": {
        "description": "Body for POST /api/suppression — manually add an email to the suppression list.",
        "properties": {
          "email": {
            "format": "email",
            "type": "string"
          },
          "reason": {
            "default": "MANUAL",
            "enum": [
              "HARD_BOUNCE",
              "COMPLAINT",
              "MANUAL",
              "UNSUBSCRIBE"
            ],
            "type": "string"
          }
        },
        "required": [
          "email"
        ],
        "type": "object"
      },
      "AnalyticsCampaignStatsV1": {
        "description": "Campaign counters and engagement over the window.",
        "properties": {
          "active": {
            "description": "Campaigns in DRAFT or SCHEDULED.",
            "type": "integer"
          },
          "average_click_rate": {
            "type": "number"
          },
          "average_open_rate": {
            "description": "Percentage, one decimal place.",
            "type": "number"
          },
          "completed": {
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "window": {
            "$ref": "#/components/schemas/AnalyticsWindowV1"
          }
        },
        "required": [
          "total",
          "active",
          "completed",
          "average_open_rate",
          "average_click_rate",
          "window"
        ],
        "type": "object"
      },
      "AnalyticsTimeseriesV1": {
        "description": "Daily email counters across the window. Every day in range is present, zero-filled.",
        "properties": {
          "data": {
            "items": {
              "properties": {
                "bounces": {
                  "type": "integer"
                },
                "clicks": {
                  "type": "integer"
                },
                "date": {
                  "format": "date-time",
                  "type": "string"
                },
                "delivered": {
                  "type": "integer"
                },
                "emails": {
                  "type": "integer"
                },
                "opens": {
                  "type": "integer"
                }
              },
              "required": [
                "date",
                "emails",
                "delivered",
                "opens",
                "clicks",
                "bounces"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "window": {
            "$ref": "#/components/schemas/AnalyticsWindowV1"
          }
        },
        "required": [
          "data",
          "window"
        ],
        "type": "object"
      },
      "AnalyticsTopCampaignsV1": {
        "description": "Sent campaigns ranked by open rate.",
        "properties": {
          "data": {
            "items": {
              "properties": {
                "click_rate": {
                  "type": "number"
                },
                "clicked": {
                  "type": "integer"
                },
                "id": {
                  "format": "uuid",
                  "type": "string"
                },
                "open_rate": {
                  "type": "number"
                },
                "opened": {
                  "type": "integer"
                },
                "sent": {
                  "type": "integer"
                },
                "subject": {
                  "type": "string"
                }
              },
              "required": [
                "id",
                "subject",
                "sent",
                "opened",
                "clicked",
                "open_rate",
                "click_rate"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "window": {
            "$ref": "#/components/schemas/AnalyticsWindowV1"
          }
        },
        "required": [
          "data",
          "window"
        ],
        "type": "object"
      },
      "AnalyticsWindowV1": {
        "description": "The time range this response was computed over, after the 90-day clamp.",
        "properties": {
          "from": {
            "format": "date-time",
            "type": "string"
          },
          "to": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "from",
          "to"
        ],
        "type": "object"
      },
      "ApiKey": {
        "description": "An API key's metadata. Never carries the token or its hash — `lastFour` is the only surviving fragment of the secret once the key has been created.",
        "properties": {
          "createdAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          },
          "domainId": {
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "lastFour": {
            "description": "Last 4 characters of the token — the only fragment of the secret that survives creation.",
            "type": "string"
          },
          "lastUsedAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "legacyGrantPreset": {
            "description": "The coarse preset the key was minted under. It picks the token prefix (`sk_` vs `pk_`) and the rate-limit window, and it is the key's authority ONLY while `scopes` is empty — which is true just of keys minted before scopes existed. Read `scopes` to learn what a key can do.",
            "enum": [
              "FULL",
              "SENDING_ONLY"
            ],
            "type": "string"
          },
          "mode": {
            "description": "Where mail sent with this key may go. `LIVE` sends from your verified domains. `TEST` can only send from this project's sandbox address, whose recipient list is your own verified account addresses, so a test key cannot reach a customer whatever it is asked to do. Fixed at creation.",
            "enum": [
              "LIVE",
              "TEST"
            ],
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "projectId": {
            "format": "uuid",
            "type": "string"
          },
          "revokedAt": {
            "description": "Set once the key is revoked. Revoked keys are NOT filtered out of list/get responses.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "scopes": {
            "description": "The explicit scope grant this key carries. Empty on a key minted before this column existed — that row's grant is derived from `legacyGrantPreset` instead at request time.",
            "items": {
              "enum": [
                "emails:send",
                "emails:read",
                "contacts:read",
                "contacts:write",
                "campaigns:read",
                "campaigns:write",
                "segments:read",
                "segments:write",
                "workflows:read",
                "workflows:write",
                "templates:read",
                "templates:write",
                "domains:read",
                "domains:write",
                "webhooks:read",
                "webhooks:write",
                "suppression:read",
                "suppression:write",
                "analytics:read",
                "usage:read",
                "events:read",
                "events:write",
                "projects:read",
                "projects:write",
                "api-keys:read",
                "api-keys:write",
                "campaigns:send",
                "mailboxes:read",
                "mailboxes:write",
                "emails:test",
                "deliverability:read",
                "mailboxes:send",
                "validation:read",
                "validation:write",
                "topics:read",
                "topics:write",
                "lists:read",
                "lists:write"
              ],
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "id",
          "projectId",
          "name",
          "lastFour",
          "legacyGrantPreset",
          "mode",
          "scopes",
          "domainId",
          "lastUsedAt",
          "createdAt",
          "revokedAt"
        ],
        "type": "object"
      },
      "ApiKeyListResponse": {
        "description": "Every API key on the project, including revoked ones — filter on `revokedAt` for live keys.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/ApiKey"
            },
            "type": "array"
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "AppPassword": {
        "description": "An IMAP/SMTP credential for one mailbox, described but never reproduced.",
        "properties": {
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "lastFour": {
            "description": "The last four characters of the secret — enough to tell two credentials apart, and nothing more.",
            "type": "string"
          },
          "lastUsedAt": {
            "description": "Null until a mail client has authenticated with it at least once.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "description": "What the credential is for, e.g. `Thunderbird on my laptop`.",
            "type": "string"
          },
          "scopes": {
            "description": "Which protocols this password may authenticate. `imap` reads, `smtp` sends.",
            "items": {
              "enum": [
                "imap",
                "smtp"
              ],
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "id",
          "name",
          "scopes",
          "lastFour",
          "lastUsedAt",
          "createdAt"
        ],
        "type": "object"
      },
      "AppPasswordReveal": {
        "description": "A newly created app password, handed over as a one-time link rather than as a secret.",
        "properties": {
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "revealExpiresAt": {
            "description": "When the link stops working. Five minutes after creation; the password itself does not expire.",
            "format": "date-time",
            "type": "string"
          },
          "revealUrl": {
            "description": "A single-use link that shows the password once, in a browser. Opening it requires a signed-in Sendly session belonging to a project admin — the connection that created the password cannot open it, and the second attempt to open it fails whoever makes it.",
            "format": "uri",
            "type": "string"
          }
        },
        "required": [
          "id",
          "revealUrl",
          "revealExpiresAt"
        ],
        "type": "object"
      },
      "AssignDomainStream": {
        "description": "Body for PATCH /api/domains/{id}.",
        "properties": {
          "defaultFromAddress": {
            "description": "The address a send on this stream uses when it names none. Must be on this identity's own host — a default pointing elsewhere would go out unsigned by the name in the From header.",
            "format": "email",
            "type": [
              "string",
              "null"
            ]
          },
          "stream": {
            "description": "Which traffic this identity carries. `null` unassigns it, returning it to serving every stream and clearing its default flag and default address.",
            "enum": [
              "TRANSACTIONAL",
              "MARKETING",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "streamDefault": {
            "description": "Make this the project's default identity for its stream, demoting whichever held it.",
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "BatchEntryResult": {
        "description": "Per-row result in a batch send response.",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/SendEmailData"
          },
          "error": {
            "properties": {
              "code": {
                "type": "string"
              },
              "message": {
                "type": "string"
              }
            },
            "required": [
              "message",
              "code"
            ],
            "type": "object"
          },
          "index": {
            "type": "integer"
          },
          "status": {
            "enum": [
              "ok",
              "error"
            ],
            "type": "string"
          }
        },
        "required": [
          "index",
          "status"
        ],
        "type": "object"
      },
      "BatchSendBody": {
        "description": "Batch send wrapper. Up to 100 entries.",
        "properties": {
          "emails": {
            "items": {
              "$ref": "#/components/schemas/SendEmail"
            },
            "maxItems": 100,
            "minItems": 1,
            "type": "array"
          }
        },
        "required": [
          "emails"
        ],
        "type": "object"
      },
      "BatchSendResponse": {
        "description": "Multi-status response for `POST /api/emails/batch`. HTTP 207 if any entry failed, else 200.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/BatchEntryResult"
            },
            "type": "array"
          },
          "success": {
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "CampaignV1": {
        "description": "A campaign as exposed on the v1 API.",
        "properties": {
          "audience_type": {
            "enum": [
              "ALL",
              "FILTERED",
              "SEGMENT",
              "LIST"
            ],
            "type": "string"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "list_id": {
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "scheduled_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "sent_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "stats": {
            "properties": {
              "bounced": {
                "type": "integer"
              },
              "clicked": {
                "type": "integer"
              },
              "delivered": {
                "type": "integer"
              },
              "opened": {
                "type": "integer"
              },
              "sent": {
                "type": "integer"
              },
              "total_recipients": {
                "type": "integer"
              }
            },
            "required": [
              "total_recipients",
              "sent",
              "delivered",
              "opened",
              "clicked",
              "bounced"
            ],
            "type": "object"
          },
          "status": {
            "enum": [
              "DRAFT",
              "SCHEDULED",
              "SENDING",
              "PAUSED",
              "SENT",
              "CANCELLED"
            ],
            "type": "string"
          },
          "subject": {
            "type": "string"
          },
          "topic_id": {
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "status",
          "subject",
          "audience_type",
          "list_id",
          "topic_id",
          "scheduled_at",
          "sent_at",
          "created_at",
          "stats"
        ],
        "type": "object"
      },
      "CampaignV1Create": {
        "description": "Body for POST /api/v1/campaigns. `segment_id` is required when `audience_type` is `SEGMENT`, `audience_condition` when it is `FILTERED`, and `list_id` when it is `LIST`.",
        "properties": {
          "audience_condition": {
            "$ref": "#/components/schemas/FilterConditionV1"
          },
          "audience_type": {
            "description": "`ALL` — every subscribed contact. `FILTERED` — the contacts matching `audience_condition`. `SEGMENT` — the members of `segment_id`. `LIST` — the CONFIRMED members of `list_id`, which is the only audience that honours double opt-in: a member who never confirmed, or who unsubscribed, is not mailed.",
            "enum": [
              "ALL",
              "FILTERED",
              "SEGMENT",
              "LIST"
            ],
            "type": "string"
          },
          "body": {
            "minLength": 1,
            "type": "string"
          },
          "description": {
            "maxLength": 500,
            "type": "string"
          },
          "email_category": {
            "default": "MARKETING",
            "enum": [
              "TRANSACTIONAL",
              "MARKETING",
              "SELF_MANAGED_UNSUBSCRIBE"
            ],
            "type": "string"
          },
          "from": {
            "description": "Sender address. Its domain must be verified for this project.",
            "format": "email",
            "type": "string"
          },
          "from_name": {
            "maxLength": 100,
            "type": [
              "string",
              "null"
            ]
          },
          "list_id": {
            "description": "Required when `audience_type` is `LIST`. Only that list's CONFIRMED members receive the campaign.",
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "maxLength": 200,
            "minLength": 1,
            "type": "string"
          },
          "reply_to": {
            "format": "email",
            "type": [
              "string",
              "null"
            ]
          },
          "segment_id": {
            "format": "uuid",
            "type": "string"
          },
          "subject": {
            "minLength": 1,
            "type": "string"
          },
          "topic_id": {
            "description": "The subject this campaign is about. A contact who unsubscribed from the topic is excluded WHATEVER the audience type — a topic opt-out is a standing answer, not an audience filter, so it cannot be routed around by selecting a different audience. Ignored for `TRANSACTIONAL` campaigns, which a marketing preference does not cancel.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "name",
          "subject",
          "body",
          "from",
          "audience_type"
        ],
        "type": "object"
      },
      "CampaignV1Deleted": {
        "description": "Acknowledgement that a campaign was deleted.",
        "properties": {
          "deleted": {
            "enum": [
              true
            ],
            "type": "boolean"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "id",
          "deleted"
        ],
        "type": "object"
      },
      "CampaignV1Failure": {
        "description": "A campaign recipient whose send did not complete.",
        "properties": {
          "contact_id": {
            "format": "uuid",
            "type": "string"
          },
          "email": {
            "description": "The recipient the send was for.",
            "type": "string"
          },
          "failed_at": {
            "format": "date-time",
            "type": "string"
          },
          "id": {
            "description": "Ledger row id. Pass the last one as `after` to page.",
            "format": "uuid",
            "type": "string"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "contact_id",
          "email",
          "reason",
          "failed_at"
        ],
        "type": "object"
      },
      "CampaignV1FailureList": {
        "description": "Cursor-paginated list of a campaign's failed sends.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/CampaignV1Failure"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "description": "Pass as `after` to fetch the next page. `null` on the last page.",
            "type": [
              "string",
              "null"
            ]
          },
          "total": {
            "description": "Every FAILED row on this campaign, not just this page.",
            "type": "integer"
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor",
          "total"
        ],
        "type": "object"
      },
      "CampaignV1List": {
        "description": "Cursor-paginated list of campaigns.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/CampaignV1"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "description": "Pass as `after` to fetch the next page. `null` on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "type": "object"
      },
      "CampaignV1RetryFailed": {
        "description": "Acknowledgement that a retry of a campaign's failed sends began.",
        "properties": {
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "queued": {
            "description": "How many FAILED rows the retry walk was started for, counted when it was queued.",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "queued"
        ],
        "type": "object"
      },
      "CampaignV1Send": {
        "description": "Body for POST /api/v1/campaigns/{id}/send.",
        "properties": {
          "scheduled_for": {
            "description": "RFC 3339 timestamp, strictly in the future. A numeric UTC offset (`+02:00`) is accepted as well as `Z`. Omit to start sending immediately.",
            "format": "date-time",
            "type": "string"
          }
        },
        "type": "object"
      },
      "CampaignV1Stats": {
        "description": "Materialized delivery and engagement counters for one campaign.",
        "properties": {
          "bounce_rate": {
            "type": "number"
          },
          "bounced": {
            "type": "integer"
          },
          "click_rate": {
            "type": "number"
          },
          "clicked": {
            "type": "integer"
          },
          "delivered": {
            "type": "integer"
          },
          "delivery_rate": {
            "type": "number"
          },
          "open_rate": {
            "type": "number"
          },
          "opened": {
            "type": "integer"
          },
          "sent": {
            "type": "integer"
          },
          "total_recipients": {
            "type": "integer"
          }
        },
        "required": [
          "total_recipients",
          "sent",
          "delivered",
          "opened",
          "clicked",
          "bounced",
          "open_rate",
          "click_rate",
          "bounce_rate",
          "delivery_rate"
        ],
        "type": "object"
      },
      "CampaignV1Update": {
        "description": "Body for PATCH /api/v1/campaigns/{id}. All fields optional.",
        "properties": {
          "audience_condition": {
            "$ref": "#/components/schemas/FilterConditionV1"
          },
          "audience_type": {
            "enum": [
              "ALL",
              "FILTERED",
              "SEGMENT",
              "LIST"
            ],
            "type": "string"
          },
          "body": {
            "minLength": 1,
            "type": "string"
          },
          "description": {
            "maxLength": 500,
            "type": "string"
          },
          "email_category": {
            "enum": [
              "TRANSACTIONAL",
              "MARKETING",
              "SELF_MANAGED_UNSUBSCRIBE"
            ],
            "type": "string"
          },
          "from": {
            "description": "Sender address. Its domain must be verified for this project.",
            "format": "email",
            "type": "string"
          },
          "from_name": {
            "maxLength": 100,
            "type": [
              "string",
              "null"
            ]
          },
          "list_id": {
            "description": "Required when `audience_type` is `LIST`. Only that list's CONFIRMED members receive the campaign.",
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "maxLength": 200,
            "minLength": 1,
            "type": "string"
          },
          "reply_to": {
            "format": "email",
            "type": [
              "string",
              "null"
            ]
          },
          "segment_id": {
            "format": "uuid",
            "type": "string"
          },
          "subject": {
            "minLength": 1,
            "type": "string"
          },
          "topic_id": {
            "description": "The subject this campaign is about. A contact who unsubscribed from the topic is excluded WHATEVER the audience type — a topic opt-out is a standing answer, not an audience filter, so it cannot be routed around by selecting a different audience. Ignored for `TRANSACTIONAL` campaigns, which a marketing preference does not cancel.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "ComposeMailboxMessage": {
        "description": "Body for POST /api/mailboxes/{id}/messages — a new outbound message from a hosted mailbox.",
        "properties": {
          "bcc": {
            "items": {
              "format": "email",
              "type": "string"
            },
            "maxItems": 20,
            "type": "array"
          },
          "body": {
            "maxLength": 50000,
            "minLength": 1,
            "type": "string"
          },
          "cc": {
            "items": {
              "format": "email",
              "type": "string"
            },
            "maxItems": 20,
            "type": "array"
          },
          "subject": {
            "maxLength": 200,
            "minLength": 1,
            "type": "string"
          },
          "to": {
            "items": {
              "format": "email",
              "type": "string"
            },
            "maxItems": 20,
            "minItems": 1,
            "type": "array"
          }
        },
        "required": [
          "to",
          "subject",
          "body"
        ],
        "type": "object"
      },
      "Contact": {
        "description": "A subscriber/contact within a project.",
        "properties": {
          "createdAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          },
          "customFields": {
            "additionalProperties": {},
            "type": [
              "object",
              "null"
            ]
          },
          "email": {
            "format": "email",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "projectId": {
            "format": "uuid",
            "type": "string"
          },
          "subscribed": {
            "type": "boolean"
          },
          "updatedAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "projectId",
          "email",
          "subscribed",
          "createdAt",
          "updatedAt"
        ],
        "type": "object"
      },
      "ContactBulkCreateBody": {
        "description": "Bulk create up to 1000 contacts.",
        "properties": {
          "contacts": {
            "items": {
              "$ref": "#/components/schemas/CreateContact"
            },
            "maxItems": 1000,
            "minItems": 1,
            "type": "array"
          }
        },
        "required": [
          "contacts"
        ],
        "type": "object"
      },
      "ContactBulkDeleteBody": {
        "description": "Bulk delete contacts. Provide either `ids` or `emails` (max 1000 each).",
        "properties": {
          "emails": {
            "items": {
              "format": "email",
              "type": "string"
            },
            "maxItems": 1000,
            "type": "array"
          },
          "ids": {
            "items": {
              "format": "uuid",
              "type": "string"
            },
            "maxItems": 1000,
            "type": "array"
          }
        },
        "type": "object"
      },
      "ContactListResponse": {
        "description": "Cursor-paginated list of contacts.",
        "properties": {
          "data": {
            "properties": {
              "data": {
                "items": {
                  "$ref": "#/components/schemas/Contact"
                },
                "type": "array"
              },
              "hasMore": {
                "type": "boolean"
              },
              "nextCursor": {
                "description": "Cursor for the next page, or null on the last page.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "total": {
                "type": "integer"
              }
            },
            "required": [
              "data",
              "total",
              "nextCursor",
              "hasMore"
            ],
            "type": "object"
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "ContactTopicPreferencesV1": {
        "description": "Everything this contact has said about what they want. `subscribed` on a topic already folds in `default_opt_in`, so a contact who has never answered still reads correctly.",
        "properties": {
          "contact_id": {
            "type": "string"
          },
          "subscribed": {
            "description": "The global marketing opt-out, which OUTRANKS every topic. False means no marketing reaches this contact whatever the topics below say.",
            "type": "boolean"
          },
          "topics": {
            "items": {
              "properties": {
                "key": {
                  "description": "Stable, URL-safe, unique within the project. This is what a preference form and an API caller name the topic by, so it must survive a rename of `name` — which is the whole reason it exists beside one.",
                  "maxLength": 64,
                  "minLength": 1,
                  "pattern": "^[a-z0-9][a-z0-9_-]*$",
                  "type": "string"
                },
                "name": {
                  "type": "string"
                },
                "pending": {
                  "type": "boolean"
                },
                "subscribed": {
                  "description": "The EFFECTIVE answer: what the send path concludes for this contact today.",
                  "type": "boolean"
                },
                "topic_id": {
                  "type": "string"
                }
              },
              "required": [
                "topic_id",
                "key",
                "name",
                "subscribed",
                "pending"
              ],
              "type": "object"
            },
            "type": "array"
          }
        },
        "required": [
          "contact_id",
          "subscribed",
          "topics"
        ],
        "type": "object"
      },
      "ContactV1": {
        "description": "A contact as exposed on the v1 API.",
        "properties": {
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "custom_fields": {
            "additionalProperties": {},
            "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
            "type": [
              "string",
              "number",
              "boolean",
              "object",
              "array",
              "null"
            ]
          },
          "email": {
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "subscribed": {
            "type": "boolean"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "email",
          "subscribed",
          "custom_fields",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "ContactV1Create": {
        "description": "Body for POST /api/v1/contacts.",
        "properties": {
          "custom_fields": {
            "additionalProperties": {},
            "description": "Arbitrary JSON stored on the contact and available to templates as `{{ variables }}`.",
            "type": [
              "string",
              "number",
              "boolean",
              "object",
              "array",
              "null"
            ]
          },
          "email": {
            "format": "email",
            "type": "string"
          },
          "subscribed": {
            "default": true,
            "type": "boolean"
          }
        },
        "required": [
          "email"
        ],
        "type": "object"
      },
      "ContactV1Deleted": {
        "description": "Acknowledgement that a contact was deleted.",
        "properties": {
          "deleted": {
            "enum": [
              true
            ],
            "type": "boolean"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "id",
          "deleted"
        ],
        "type": "object"
      },
      "ContactV1List": {
        "description": "Cursor-paginated list of contacts.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/ContactV1"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "description": "Pass as `after` to fetch the next page. `null` on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "type": "object"
      },
      "ContactV1Update": {
        "description": "Body for PATCH /api/v1/contacts/{id}. `email` is deliberately absent: an address is the contact's identity on this API, and changing it in place would silently rewrite what every earlier send was addressed to. Create the new address instead.",
        "properties": {
          "custom_fields": {
            "additionalProperties": {},
            "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
            "type": [
              "string",
              "number",
              "boolean",
              "object",
              "array",
              "null"
            ]
          },
          "subscribed": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "CreateApiKeyBody": {
        "properties": {
          "domainId": {
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "legacyGrantPreset": {
            "enum": [
              "FULL",
              "SENDING_ONLY"
            ],
            "type": "string"
          },
          "mode": {
            "description": "`LIVE` (default) or `TEST`. A test key can only send from the project's sandbox address, which accepts only the project's own verified account addresses as recipients, so it cannot reach a customer. Its sends are real sends down the same pipeline and are marked `sentInTestMode`. Fixed at creation.",
            "enum": [
              "LIVE",
              "TEST"
            ],
            "type": "string"
          },
          "name": {
            "maxLength": 120,
            "minLength": 1,
            "type": "string"
          },
          "scopes": {
            "description": "The explicit grant the new key will carry. Omitted ⇒ materialised from `legacyGrantPreset`. A `SENDING_ONLY` key may carry only `emails:send`.",
            "items": {
              "enum": [
                "emails:send",
                "emails:read",
                "contacts:read",
                "contacts:write",
                "campaigns:read",
                "campaigns:write",
                "segments:read",
                "segments:write",
                "workflows:read",
                "workflows:write",
                "templates:read",
                "templates:write",
                "domains:read",
                "domains:write",
                "webhooks:read",
                "webhooks:write",
                "suppression:read",
                "suppression:write",
                "analytics:read",
                "usage:read",
                "events:read",
                "events:write",
                "projects:read",
                "projects:write",
                "api-keys:read",
                "api-keys:write",
                "campaigns:send",
                "mailboxes:read",
                "mailboxes:write",
                "emails:test",
                "deliverability:read",
                "mailboxes:send",
                "validation:read",
                "validation:write",
                "topics:read",
                "topics:write",
                "lists:read",
                "lists:write"
              ],
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "CreateAppPassword": {
        "description": "Body for POST /api/mailboxes/:id/app-passwords.",
        "properties": {
          "name": {
            "maxLength": 60,
            "minLength": 1,
            "type": "string"
          },
          "scopes": {
            "default": [
              "imap",
              "smtp"
            ],
            "items": {
              "enum": [
                "imap",
                "smtp"
              ],
              "type": "string"
            },
            "minItems": 1,
            "type": "array"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "CreateContact": {
        "description": "Body for POST /api/contacts and /api/contacts/upsert.",
        "properties": {
          "customFields": {
            "additionalProperties": {},
            "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
            "type": "object"
          },
          "email": {
            "format": "email",
            "type": "string"
          },
          "subscribed": {
            "default": true,
            "type": "boolean"
          }
        },
        "required": [
          "email"
        ],
        "type": "object"
      },
      "CreateMailboxBody": {
        "properties": {
          "displayName": {
            "maxLength": 120,
            "type": "string"
          },
          "domainId": {
            "description": "A VERIFIED domain belonging to this project.",
            "format": "uuid",
            "type": "string"
          },
          "localPart": {
            "description": "The part before the `@`, e.g. `support`. Lowercased server-side.",
            "maxLength": 64,
            "minLength": 1,
            "type": "string"
          },
          "projectId": {
            "description": "Defaults to the project the credential resolves to. Naming a different one is refused.",
            "format": "uuid",
            "type": "string"
          },
          "quotaBytes": {
            "description": "NOT IMPLEMENTED — sending any value answers 400.",
            "exclusiveMinimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "domainId",
          "localPart"
        ],
        "type": "object"
      },
      "CreateSnippet": {
        "description": "Body for POST /api/snippets.",
        "properties": {
          "body": {
            "maxLength": 20000,
            "minLength": 1,
            "type": "string"
          },
          "description": {
            "maxLength": 500,
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "pattern": "^[a-z][\\da-z_-]{0,63}$/i",
            "type": "string"
          }
        },
        "required": [
          "name",
          "body"
        ],
        "type": "object"
      },
      "CreateTemplate": {
        "description": "Body for POST /api/templates.",
        "properties": {
          "body": {
            "minLength": 1,
            "type": "string"
          },
          "description": {
            "maxLength": 500,
            "type": "string"
          },
          "emailCategory": {
            "default": "MARKETING",
            "enum": [
              "TRANSACTIONAL",
              "MARKETING",
              "SELF_MANAGED_UNSUBSCRIBE"
            ],
            "type": "string"
          },
          "from": {
            "format": "email",
            "type": "string"
          },
          "fromName": {
            "maxLength": 100,
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "maxLength": 100,
            "minLength": 1,
            "type": "string"
          },
          "replyTo": {
            "format": "email",
            "type": [
              "string",
              "null"
            ]
          },
          "subject": {
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "name",
          "subject",
          "body",
          "from"
        ],
        "type": "object"
      },
      "CreateWebhook": {
        "description": "Body for POST /api/webhooks — register a user webhook for one or more events.",
        "properties": {
          "eventTypes": {
            "items": {
              "enum": [
                "email.sent",
                "email.delivered",
                "email.opened",
                "email.clicked",
                "email.bounced",
                "email.complained",
                "email.failed",
                "contact.created",
                "contact.unsubscribed",
                "contacts.bulk_created"
              ],
              "type": "string"
            },
            "minItems": 1,
            "type": "array"
          },
          "url": {
            "format": "uri",
            "type": "string"
          }
        },
        "required": [
          "url",
          "eventTypes"
        ],
        "type": "object"
      },
      "DeliverabilityDiagnosisV1": {
        "description": "A composed answer to why mail from one domain may not be arriving: its DNS identity, the project's recent delivery outcomes, one address's suppression state, and the findings drawn from them.",
        "properties": {
          "address": {
            "type": [
              "string",
              "null"
            ]
          },
          "checked_at": {
            "format": "date-time",
            "type": "string"
          },
          "domain": {
            "type": "string"
          },
          "findings": {
            "description": "What is wrong, worst first. An empty array means nothing here explains a delivery problem.",
            "items": {
              "$ref": "#/components/schemas/DeliverabilityFindingV1"
            },
            "type": "array"
          },
          "identity": {
            "$ref": "#/components/schemas/DeliverabilityIdentityV1"
          },
          "recent_delivery": {
            "$ref": "#/components/schemas/DeliverabilityRecentDeliveryV1"
          },
          "suppression": {
            "$ref": "#/components/schemas/DeliverabilitySuppressionV1"
          }
        },
        "required": [
          "domain",
          "address",
          "checked_at",
          "identity",
          "suppression",
          "recent_delivery",
          "findings"
        ],
        "type": "object"
      },
      "DeliverabilityFindingSeverityV1": {
        "description": "`blocking`: mail from this domain cannot be delivered as configured. `degraded`: it delivers, but inbox placement or sender reputation is at risk. `info`: worth knowing, nothing to fix.",
        "enum": [
          "blocking",
          "degraded",
          "info"
        ],
        "type": "string"
      },
      "DeliverabilityFindingV1": {
        "description": "One diagnosed problem, with its fix.",
        "properties": {
          "code": {
            "description": "Stable identifier for this finding, e.g. `domain_not_verified`. Branch on this, not on `summary`.",
            "type": "string"
          },
          "remedy": {
            "description": "What to do about it.",
            "type": "string"
          },
          "severity": {
            "$ref": "#/components/schemas/DeliverabilityFindingSeverityV1"
          },
          "summary": {
            "description": "What is wrong, in one sentence.",
            "type": "string"
          }
        },
        "required": [
          "code",
          "severity",
          "summary",
          "remedy"
        ],
        "type": "object"
      },
      "DeliverabilityIdentityV1": {
        "description": "The sending identity's DNS health, as last refreshed.",
        "properties": {
          "dkim_status": {
            "description": "DKIM signing. This is the one that decides whether Sendly will send from the domain at all.",
            "enum": [
              "NOT_CHECKED",
              "PENDING",
              "VERIFIED",
              "FAILED",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "dmarc_status": {
            "description": "The DMARC policy published at `_dmarc.<domain>`.",
            "enum": [
              "NOT_CHECKED",
              "PENDING",
              "VERIFIED",
              "FAILED",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "last_checked_at": {
            "description": "When the DNS refresh job last looked. These statuses are a CACHE, not a live lookup.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "mail_from_domain": {
            "type": [
              "string",
              "null"
            ]
          },
          "mail_from_domain_status": {
            "description": "Raw SES `CustomMailFromStatus` (`Pending`/`Success`/`Failed`/`TemporaryFailure`), or `NotConfigured`. `Failed` means SES silently fell back to `amazonses.com`, which is why the value is reported rather than folded into a boolean.",
            "type": [
              "string",
              "null"
            ]
          },
          "mx_status": {
            "description": "Inbound receiving only. Null unless the domain has receiving enabled.",
            "enum": [
              "NOT_CHECKED",
              "PENDING",
              "VERIFIED",
              "FAILED",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "registered": {
            "description": "Whether this project has a domain record at all. False makes every other field null.",
            "type": "boolean"
          },
          "spf_status": {
            "description": "SPF alignment for the sending identity.",
            "enum": [
              "NOT_CHECKED",
              "PENDING",
              "VERIFIED",
              "FAILED",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "verified": {
            "type": "boolean"
          }
        },
        "required": [
          "registered",
          "verified",
          "dkim_status",
          "spf_status",
          "dmarc_status",
          "mx_status",
          "mail_from_domain",
          "mail_from_domain_status",
          "last_checked_at"
        ],
        "type": "object"
      },
      "DeliverabilityRecentDeliveryV1": {
        "description": "Delivery outcomes over the requested window.",
        "properties": {
          "bounce_rate": {
            "description": "Bounced ÷ sent (0–1), or null when nothing was sent in the window.",
            "type": [
              "number",
              "null"
            ]
          },
          "bounced": {
            "type": "integer"
          },
          "complained": {
            "type": "integer"
          },
          "complaint_rate": {
            "type": [
              "number",
              "null"
            ]
          },
          "delivered": {
            "type": "integer"
          },
          "failed": {
            "type": "integer"
          },
          "scope": {
            "description": "PROJECT-WIDE, not per-domain, and said so rather than implied. `Email` has no sending-domain column, so narrowing these counters to one domain would mean a scan over every row the project has ever sent. A project that sends from one domain — most of them — can read these as that domain's.",
            "enum": [
              "project"
            ],
            "type": "string"
          },
          "sent": {
            "type": "integer"
          },
          "window_days": {
            "type": "integer"
          }
        },
        "required": [
          "window_days",
          "scope",
          "sent",
          "delivered",
          "bounced",
          "complained",
          "failed",
          "bounce_rate",
          "complaint_rate"
        ],
        "type": "object"
      },
      "DeliverabilitySuppressionV1": {
        "description": "Null unless the request named an `address`.",
        "properties": {
          "reason": {
            "enum": [
              "HARD_BOUNCE",
              "COMPLAINT",
              "MANUAL",
              "UNSUBSCRIBE",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "enum": [
              "SES_WEBHOOK",
              "API",
              "DASHBOARD",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "suppressed": {
            "type": "boolean"
          },
          "suppressed_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "suppressed",
          "reason",
          "source",
          "suppressed_at"
        ],
        "type": [
          "object",
          "null"
        ]
      },
      "DmarcReportV1": {
        "description": "One DMARC aggregate (RUA) report.",
        "properties": {
          "fail_count": {
            "type": "integer"
          },
          "id": {
            "type": "string"
          },
          "org_name": {
            "description": "The reporting receiver, e.g. `google.com`.",
            "type": "string"
          },
          "pass_count": {
            "description": "Messages DMARC-ALIGNED (SPF or DKIM aligned and passing), from `policy_evaluated`. Not the raw auth results: a message can pass SPF for a domain that is not the one in its From header, which is the case DMARC exists to catch.",
            "type": "integer"
          },
          "policy_domain": {
            "description": "The domain of yours the report is about.",
            "type": "string"
          },
          "range_begin": {
            "format": "date-time",
            "type": "string"
          },
          "range_end": {
            "format": "date-time",
            "type": "string"
          },
          "received_at": {
            "format": "date-time",
            "type": "string"
          },
          "report_id": {
            "description": "The receiver's own id for this report.",
            "type": "string"
          },
          "sources": {
            "description": "Per-sending-source rows, as the receiver reported them.",
            "items": {
              "properties": {
                "count": {
                  "type": "integer"
                },
                "disposition": {
                  "type": "string"
                },
                "dkim": {
                  "type": "string"
                },
                "header_from": {
                  "type": "string"
                },
                "source_ip": {
                  "type": "string"
                },
                "spf": {
                  "type": "string"
                }
              },
              "required": [
                "source_ip",
                "count",
                "disposition",
                "dkim",
                "spf",
                "header_from"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "total_count": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "report_id",
          "org_name",
          "policy_domain",
          "range_begin",
          "range_end",
          "total_count",
          "pass_count",
          "fail_count",
          "sources",
          "received_at"
        ],
        "type": "object"
      },
      "DmarcReportV1List": {
        "description": "Cursor-paginated DMARC aggregate reports, newest window first.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/DmarcReportV1"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "intake_configured": {
            "description": "Whether this deployment has a DMARC report intake mailbox configured. When `false` no report can ever arrive, so an empty `data` means the feature is off rather than that your domains are clean — the two are otherwise indistinguishable.",
            "type": "boolean"
          },
          "next_cursor": {
            "description": "Pass as `after` to fetch the next page. `null` on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor",
          "intake_configured"
        ],
        "type": "object"
      },
      "Domain": {
        "description": "A sending identity: one domain registered with SES, with its own DKIM keys, its own MAIL FROM and its own reputation.",
        "properties": {
          "createdAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          },
          "defaultFromAddress": {
            "description": "The address a send on this stream uses when it names none. Always on this identity's own host.",
            "type": [
              "string",
              "null"
            ]
          },
          "dkimStatus": {
            "description": "Result of the last DNS check for this record type.",
            "enum": [
              "NOT_CHECKED",
              "PENDING",
              "VERIFIED",
              "FAILED",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "dkimTokens": {
            "description": "SES DKIM tokens to publish as CNAME records before the domain can verify.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "dmarcStatus": {
            "description": "Result of the last DNS check for this record type.",
            "enum": [
              "NOT_CHECKED",
              "PENDING",
              "VERIFIED",
              "FAILED",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "domain": {
            "description": "The bare domain, e.g. `mail.acme.com`.",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "lastHealthCheckAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "mailFromDomain": {
            "description": "Custom MAIL FROM subdomain SES has on record (normally `sendly.<domain>`).",
            "type": [
              "string",
              "null"
            ]
          },
          "mailFromDomainStatus": {
            "description": "SES custom MAIL FROM setup state. Only `Success` means SES is using it.",
            "enum": [
              "Pending",
              "Success",
              "Failed",
              "TemporaryFailure",
              "NotConfigured",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "projectId": {
            "format": "uuid",
            "type": "string"
          },
          "receivingEnabled": {
            "description": "Whether inbound mail for this domain is routed to Sendly mailboxes.",
            "type": "boolean"
          },
          "region": {
            "type": [
              "string",
              "null"
            ]
          },
          "spfStatus": {
            "description": "Result of the last DNS check for this record type.",
            "enum": [
              "NOT_CHECKED",
              "PENDING",
              "VERIFIED",
              "FAILED",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "stream": {
            "description": "Which traffic this identity carries. `null` means unassigned, and an unassigned identity carries every stream — the behaviour of every domain added before per-stream identities. A send whose stream does not match an ASSIGNED identity is refused.",
            "enum": [
              "TRANSACTIONAL",
              "MARKETING",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "streamDefault": {
            "description": "Whether this is the project's default identity for its stream — the one a send picks when it names no from-address. At most one per (project, stream).",
            "type": "boolean"
          },
          "updatedAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          },
          "verified": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "projectId",
          "domain",
          "verified",
          "receivingEnabled",
          "createdAt",
          "updatedAt"
        ],
        "type": "object"
      },
      "DomainListResponse": {
        "description": "List of all domains for the auth'd project.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/Domain"
            },
            "type": "array"
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "DomainV1": {
        "description": "A sending domain as exposed on the v1 API.",
        "properties": {
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "default_from_address": {
            "type": [
              "string",
              "null"
            ]
          },
          "dkim_verified": {
            "type": "boolean"
          },
          "domain": {
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "mail_from_domain": {
            "type": [
              "string",
              "null"
            ]
          },
          "mail_from_domain_status": {
            "description": "SES's CustomMailFromStatus for `mail_from_domain` — the subdomain that carries the bounce path, NOT the status of any From address.",
            "type": [
              "string",
              "null"
            ]
          },
          "region": {
            "type": [
              "string",
              "null"
            ]
          },
          "stream": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SendingStream"
              },
              {
                "description": "Which traffic a sending identity carries. An identity with no stream serves both, which is how every domain added before per-stream identities behaves.",
                "type": [
                  "string",
                  "null"
                ]
              }
            ]
          },
          "stream_default": {
            "type": "boolean"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string"
          },
          "verified": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "domain",
          "verified",
          "region",
          "stream",
          "stream_default",
          "default_from_address",
          "mail_from_domain",
          "mail_from_domain_status",
          "dkim_verified",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "DomainV1Create": {
        "description": "Body for POST /api/v1/domains.",
        "properties": {
          "domain": {
            "maxLength": 253,
            "minLength": 3,
            "type": "string"
          },
          "region": {
            "description": "AWS SES region for the project. Once a domain is added the region is locked and cannot be changed.",
            "enum": [
              "us-east-1",
              "us-west-2",
              "eu-west-1"
            ],
            "type": "string"
          },
          "stream": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SendingStream"
              },
              {
                "description": "Which traffic a sending identity carries. An identity with no stream serves both, which is how every domain added before per-stream identities behaves."
              }
            ]
          },
          "stream_default": {
            "description": "Make this the project's default identity for `stream`. Requires `stream`.",
            "type": "boolean"
          }
        },
        "required": [
          "domain"
        ],
        "type": "object"
      },
      "DomainV1Deleted": {
        "description": "Acknowledgement that a sending domain was removed.",
        "properties": {
          "deleted": {
            "enum": [
              true
            ],
            "type": "boolean"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "id",
          "deleted"
        ],
        "type": "object"
      },
      "DomainV1List": {
        "description": "Cursor-paginated list of sending domains.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/DomainV1"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "description": "Pass as `after` to fetch the next page. `null` on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "type": "object"
      },
      "DomainVerificationStatus": {
        "description": "Outcome of a verification check against SES.",
        "properties": {
          "dkimStatus": {
            "enum": [
              "VERIFIED",
              "PENDING",
              "FAILED"
            ],
            "type": "string"
          },
          "dmarcStatus": {
            "enum": [
              "VERIFIED",
              "FAILED",
              "NOT_CHECKED"
            ],
            "type": "string"
          },
          "domain": {
            "type": "string"
          },
          "mailFromDomain": {
            "type": [
              "string",
              "null"
            ]
          },
          "mailFromDomainStatus": {
            "description": "SES custom MAIL FROM setup state. Only `Success` means SES is using it.",
            "enum": [
              "Pending",
              "Success",
              "Failed",
              "TemporaryFailure",
              "NotConfigured",
              null
            ],
            "type": [
              "string",
              "null"
            ]
          },
          "spfStatus": {
            "enum": [
              "VERIFIED",
              "FAILED",
              "NOT_CHECKED"
            ],
            "type": "string"
          },
          "status": {
            "description": "Raw SES DKIM verification status, e.g. `Success` or `Pending`.",
            "type": "string"
          },
          "tokens": {
            "description": "DKIM tokens SES still has to report. Absent once verification has resolved.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "verified": {
            "type": "boolean"
          }
        },
        "required": [
          "domain",
          "status",
          "verified",
          "dkimStatus",
          "spfStatus",
          "dmarcStatus",
          "mailFromDomain"
        ],
        "type": "object"
      },
      "DraftMailboxMessage": {
        "description": "Body for POST /api/mailboxes/{id}/drafts — ask for help writing, never for sending.",
        "properties": {
          "brief": {
            "maxLength": 4000,
            "type": "string"
          },
          "draft": {
            "maxLength": 20000,
            "type": "string"
          },
          "instruction": {
            "maxLength": 500,
            "type": "string"
          },
          "mode": {
            "enum": [
              "draft",
              "rewrite",
              "subject"
            ],
            "type": "string"
          },
          "recipientContext": {
            "maxLength": 2000,
            "type": "string"
          },
          "senderAddress": {
            "maxLength": 320,
            "type": "string"
          },
          "tone": {
            "enum": [
              "friendly",
              "neutral",
              "formal",
              "apologetic",
              "direct"
            ],
            "type": "string"
          }
        },
        "required": [
          "mode"
        ],
        "type": "object"
      },
      "Email": {
        "description": "A sent (or queued) transactional email.",
        "properties": {
          "bouncedAt": {
            "description": "Bounced, or null.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "clickedAt": {
            "description": "First click, or null.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "clicks": {
            "description": "Total clicks recorded.",
            "type": "integer"
          },
          "complainedAt": {
            "description": "Spam complaint, or null.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "createdAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          },
          "deliveredAt": {
            "description": "Accepted by the recipient's server, or null.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "error": {
            "type": [
              "string",
              "null"
            ]
          },
          "from": {
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "openedAt": {
            "description": "First open, or null.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "opens": {
            "description": "Total opens recorded.",
            "type": "integer"
          },
          "projectId": {
            "format": "uuid",
            "type": "string"
          },
          "sentAt": {
            "description": "Handed to the provider, or null.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/components/schemas/EmailDeliveryStatus"
          },
          "subject": {
            "type": "string"
          },
          "tags": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "to": {
            "type": "string"
          },
          "updatedAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "projectId",
          "from",
          "to",
          "subject",
          "status",
          "sentAt",
          "deliveredAt",
          "bouncedAt",
          "openedAt",
          "clickedAt",
          "complainedAt",
          "opens",
          "clicks",
          "tags",
          "createdAt",
          "updatedAt"
        ],
        "type": "object"
      },
      "EmailDeliveryStatus": {
        "description": "Delivery lifecycle of the message. Engagement is reported separately.",
        "enum": [
          "PENDING",
          "SENDING",
          "SENT",
          "DELIVERED",
          "RECEIVED",
          "BOUNCED",
          "FAILED",
          "REJECTED",
          "RENDERING_FAILURE",
          "DELIVERY_DELAY",
          "CANCELLED"
        ],
        "type": "string"
      },
      "EmailDetailResponse": {
        "description": "One email and its delivery history.",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/EmailWithEvents"
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "EmailEvent": {
        "description": "One transition in a message's delivery history.",
        "properties": {
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/EmailDeliveryStatus"
          },
          "timestamp": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "status",
          "timestamp"
        ],
        "type": "object"
      },
      "EmailListResponse": {
        "description": "Cursor-paginated list of emails.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/Email"
            },
            "type": "array"
          },
          "nextCursor": {
            "type": [
              "string",
              "null"
            ]
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "EmailResponse": {
        "description": "A single email.",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Email"
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "EmailTestV1": {
        "description": "Receipt for a sandbox test send.",
        "properties": {
          "from": {
            "description": "This project's sandbox sender — resolved server-side, never from the body.",
            "format": "email",
            "type": "string"
          },
          "id": {
            "description": "The Email row this send created.",
            "format": "uuid",
            "type": "string"
          },
          "sandbox": {
            "description": "Always true. It is here so a model relaying this result cannot describe a test send as a real one: the message came from the shared sandbox domain and could only reach the project owner's own inbox.",
            "enum": [
              true
            ],
            "type": "boolean"
          },
          "status": {
            "description": "Delivery status at the moment of the response — `PENDING` for a send still queued.",
            "enum": [
              "PENDING",
              "SENDING",
              "SENT",
              "DELIVERED",
              "RECEIVED",
              "BOUNCED",
              "FAILED",
              "REJECTED",
              "RENDERING_FAILURE",
              "DELIVERY_DELAY",
              "CANCELLED"
            ],
            "type": "string"
          },
          "to": {
            "description": "The recipient the message was queued for.",
            "format": "email",
            "type": "string"
          }
        },
        "required": [
          "id",
          "status",
          "to",
          "from",
          "sandbox"
        ],
        "type": "object"
      },
      "EmailV1": {
        "description": "Receipt for a single transactional send.",
        "properties": {
          "from": {
            "description": "The sender actually used. Worth reading rather than assuming: it is resolved server-side and may come from the template when the request named none.",
            "format": "email",
            "type": "string"
          },
          "id": {
            "description": "The Email row this send created. Quote it in support requests.",
            "format": "uuid",
            "type": "string"
          },
          "status": {
            "description": "Delivery status at the moment of the response — `PENDING` for a send the worker has not picked up yet, which is the usual answer. Later states arrive via webhooks, not here.",
            "enum": [
              "PENDING",
              "SENDING",
              "SENT",
              "DELIVERED",
              "RECEIVED",
              "BOUNCED",
              "FAILED",
              "REJECTED",
              "RENDERING_FAILURE",
              "DELIVERY_DELAY",
              "CANCELLED"
            ],
            "type": "string"
          },
          "to": {
            "description": "The recipient the message was queued for.",
            "format": "email",
            "type": "string"
          }
        },
        "required": [
          "id",
          "status",
          "to",
          "from"
        ],
        "type": "object"
      },
      "EmailValidationBatchRequestV1": {
        "properties": {
          "emails": {
            "description": "The addresses to check, at most 50. Every distinct DOMAIN costs a DNS round trip, so this endpoint is bounded by latency rather than payload size — validate a whole list with `POST /api/v1/lists/{id}/validation-runs`, which is a background job.",
            "items": {
              "format": "email",
              "type": "string"
            },
            "maxItems": 50,
            "minItems": 1,
            "type": "array"
          }
        },
        "required": [
          "emails"
        ],
        "type": "object"
      },
      "EmailValidationBatchV1": {
        "description": "One verdict per address, in the order they were given.",
        "properties": {
          "results": {
            "items": {
              "$ref": "#/components/schemas/EmailValidationV1"
            },
            "type": "array"
          }
        },
        "required": [
          "results"
        ],
        "type": "object"
      },
      "EmailValidationResultListV1": {
        "description": "One page of a run's results. No total — a run over a large list holds millions of rows, and the run's own counters are the numbers worth reading.",
        "properties": {
          "data": {
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/EmailValidationV1"
                },
                {
                  "properties": {
                    "contact_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "contact_id"
                  ],
                  "type": "object"
                }
              ],
              "description": "One address's verdict, with the evidence behind it."
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "description": "Pass as `after` to fetch the next page. `null` on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "type": "object"
      },
      "EmailValidationRunV1": {
        "description": "One bulk validation run over a list.",
        "properties": {
          "completed_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "deliverable_count": {
            "type": "integer"
          },
          "failure_reason": {
            "description": "Set only on `failed`. Prose for an operator; never parse it.",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "type": "string"
          },
          "list_id": {
            "type": [
              "string",
              "null"
            ]
          },
          "processed_count": {
            "description": "Addresses checked so far. There is deliberately no total: a list changes size while a run walks it, so a denominator captured up front would be wrong by the time you read it.",
            "type": "integer"
          },
          "risky_count": {
            "type": "integer"
          },
          "started_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "enum": [
              "pending",
              "running",
              "completed",
              "failed"
            ],
            "type": "string"
          },
          "undeliverable_count": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "list_id",
          "status",
          "processed_count",
          "deliverable_count",
          "undeliverable_count",
          "risky_count",
          "started_at",
          "completed_at",
          "failure_reason",
          "created_at"
        ],
        "type": "object"
      },
      "EmailValidationV1": {
        "description": "One address's verdict, with the evidence behind it.",
        "properties": {
          "email": {
            "type": "string"
          },
          "has_mx_records": {
            "description": "The domain publishes MX records.",
            "type": "boolean"
          },
          "is_disposable": {
            "description": "A throwaway-inbox provider. The ONLY flag here that lowers the verdict.",
            "type": "boolean"
          },
          "is_personal": {
            "description": "A free/consumer provider (Gmail, Outlook). List-quality information, not a problem.",
            "type": "boolean"
          },
          "is_role_address": {
            "description": "The local part addresses a role (`support@`, `info@`), not a person. List-quality information: role mailboxes are deliverable and companies answer them.",
            "type": "boolean"
          },
          "reasons": {
            "description": "Human-readable findings. Prose for a person to read — branch on `verdict`, never on these.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "verdict": {
            "$ref": "#/components/schemas/EmailValidationVerdictV1"
          }
        },
        "required": [
          "email",
          "verdict",
          "is_disposable",
          "is_role_address",
          "is_personal",
          "has_mx_records",
          "reasons"
        ],
        "type": "object"
      },
      "EmailValidationVerdictV1": {
        "description": "`deliverable`: the domain resolves and accepts mail, with no badness signal. `undeliverable`: the domain does not exist or publishes no MX records. `risky`: deliverable, but a throwaway-inbox provider — mailing it costs sender reputation. `unknown`: DNS did not answer in time, so this address was NOT checked. Ask again; never delete a contact on `unknown`.",
        "enum": [
          "deliverable",
          "undeliverable",
          "risky",
          "unknown"
        ],
        "type": "string"
      },
      "EmailWithEvents": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Email"
          },
          {
            "properties": {
              "events": {
                "description": "Delivery transitions for this message, oldest first. NOT the custom events recorded with `POST /api/v1/events` — those are a separate resource.",
                "items": {
                  "$ref": "#/components/schemas/EmailEvent"
                },
                "type": "array"
              }
            },
            "required": [
              "events"
            ],
            "type": "object"
          }
        ],
        "description": "A transactional email together with its delivery history."
      },
      "Error": {
        "description": "Standard error envelope returned by all 4xx/5xx responses. Migrated routes include `success: false`; 422 validation errors add `error.details.errors`.",
        "properties": {
          "error": {
            "properties": {
              "code": {
                "type": "string"
              },
              "details": {
                "properties": {
                  "errors": {
                    "items": {},
                    "type": "array"
                  }
                },
                "required": [
                  "errors"
                ],
                "type": "object"
              },
              "message": {
                "type": "string"
              }
            },
            "required": [
              "message",
              "code"
            ],
            "type": "object"
          },
          "success": {
            "enum": [
              false
            ],
            "type": "boolean"
          }
        },
        "required": [
          "error"
        ],
        "type": "object"
      },
      "EventNamesV1": {
        "description": "Every distinct event name in the project, most frequent first.",
        "properties": {
          "data": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "data"
        ],
        "type": "object"
      },
      "EventStatsV1": {
        "description": "Per-name event counts over the applied window.",
        "properties": {
          "data": {
            "items": {
              "properties": {
                "count": {
                  "type": "integer"
                },
                "name": {
                  "type": "string"
                }
              },
              "required": [
                "name",
                "count"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "window": {
            "$ref": "#/components/schemas/AnalyticsWindowV1"
          }
        },
        "required": [
          "data",
          "window"
        ],
        "type": "object"
      },
      "EventTrackV1": {
        "description": "Body for POST /api/v1/events.",
        "properties": {
          "contact_id": {
            "description": "Contact the event belongs to. Must already exist in this project — unlike the legacy `POST /api/track`, this endpoint never creates contacts. Omit for a project-level event.",
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "description": "Event name, e.g. `user.signup`.",
            "maxLength": 200,
            "minLength": 1,
            "type": "string"
          },
          "payload": {
            "additionalProperties": {
              "additionalProperties": {},
              "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
              "type": [
                "string",
                "number",
                "boolean",
                "object",
                "array",
                "null"
              ]
            },
            "description": "Arbitrary event payload.",
            "type": "object"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "EventV1": {
        "description": "A recorded custom event.",
        "properties": {
          "contact_id": {
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "email_id": {
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "payload": {
            "additionalProperties": {},
            "description": "The payload recorded with the event, or null.",
            "type": [
              "object",
              "null"
            ]
          }
        },
        "required": [
          "id",
          "name",
          "contact_id",
          "email_id",
          "payload",
          "created_at"
        ],
        "type": "object"
      },
      "EventV1List": {
        "description": "Cursor-paginated list of events, newest first.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/EventV1"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "description": "Pass as `after` to fetch the next page. `null` on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "type": "object"
      },
      "FilterConditionV1": {
        "description": "A filter condition: one or more groups combined with `logic`.",
        "properties": {
          "groups": {
            "items": {
              "$ref": "#/components/schemas/FilterGroupV1"
            },
            "minItems": 1,
            "type": "array"
          },
          "logic": {
            "enum": [
              "AND",
              "OR"
            ],
            "type": "string"
          }
        },
        "required": [
          "logic",
          "groups"
        ],
        "type": "object"
      },
      "FilterGroupV1": {
        "description": "A group of filters. The filters inside one group ALWAYS combine with AND; `conditions` nests a further condition under this group, which is how OR-of-ANDs (and deeper) is expressed.",
        "properties": {
          "conditions": {
            "$ref": "#/components/schemas/FilterConditionV1"
          },
          "filters": {
            "items": {
              "$ref": "#/components/schemas/SegmentFilterV1"
            },
            "type": "array"
          }
        },
        "required": [
          "filters"
        ],
        "type": "object"
      },
      "IdResponse": {
        "description": "Success envelope carrying the affected resource's id, e.g. after a delete.",
        "properties": {
          "data": {
            "properties": {
              "id": {
                "format": "uuid",
                "type": "string"
              }
            },
            "required": [
              "id"
            ],
            "type": "object"
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "ListSubscribe": {
        "description": "Body for POST /api/lists/{id}/subscribe.",
        "properties": {
          "allowResubscribe": {
            "default": false,
            "description": "Permission to reverse an earlier opt-out. When the email already has an UNSUBSCRIBED membership on this list, the call fails with 409 RESUBSCRIBE_CONFIRMATION_REQUIRED unless this is `true`. Send `true` only when the contact is acting for themselves — a public subscribe form they submitted is affirmative consent — never for an operator-initiated add.",
            "type": "boolean"
          },
          "data": {
            "additionalProperties": {},
            "description": "Custom fields to upsert onto the contact as part of subscribing.",
            "type": "object"
          },
          "email": {
            "format": "email",
            "type": "string"
          }
        },
        "required": [
          "email"
        ],
        "type": "object"
      },
      "ListSubscribeResponse": {
        "description": "Result of a list-subscribe call.",
        "properties": {
          "data": {
            "properties": {
              "confirmToken": {
                "description": "Present only when the list has doubleOptIn enabled. Sendly does not send the confirmation email — deliver /api/lists/confirm-subscription?token=<confirmToken> to the contact. Valid for 24 hours.",
                "type": "string"
              },
              "created": {
                "description": "True when the membership row did not exist before this call.",
                "type": "boolean"
              },
              "membershipId": {
                "format": "uuid",
                "type": "string"
              },
              "previousStatus": {
                "description": "Status the membership held before this call; null when it did not exist. Use this rather than `created` to describe the transition to the user.",
                "enum": [
                  "PENDING",
                  "CONFIRMED",
                  "UNSUBSCRIBED",
                  null
                ],
                "type": [
                  "string",
                  "null"
                ]
              },
              "status": {
                "enum": [
                  "PENDING",
                  "CONFIRMED",
                  "UNSUBSCRIBED"
                ],
                "type": "string"
              }
            },
            "required": [
              "membershipId",
              "status",
              "created",
              "previousStatus"
            ],
            "type": "object"
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "ListUnsubscribe": {
        "description": "Body for POST /api/lists/{id}/unsubscribe.",
        "properties": {
          "email": {
            "format": "email",
            "type": "string"
          }
        },
        "required": [
          "email"
        ],
        "type": "object"
      },
      "ListUnsubscribeResponse": {
        "description": "Echoes the address that was unsubscribed.",
        "properties": {
          "data": {
            "properties": {
              "email": {
                "format": "email",
                "type": "string"
              }
            },
            "required": [
              "email"
            ],
            "type": "object"
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "ListV1": {
        "description": "A subscriber list as exposed on the v1 API.",
        "properties": {
          "confirmation_template_id": {
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "double_opt_in": {
            "type": "boolean"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "member_count": {
            "description": "Memberships in ANY status, including PENDING and UNSUBSCRIBED ones.",
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "redirect_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "updated_at": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "double_opt_in",
          "confirmation_template_id",
          "redirect_url",
          "member_count",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "ListV1Create": {
        "description": "Body for POST /api/v1/lists.",
        "properties": {
          "confirmation_template_id": {
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "maxLength": 500,
            "type": [
              "string",
              "null"
            ]
          },
          "double_opt_in": {
            "default": false,
            "description": "Require the contact to confirm before the membership becomes CONFIRMED. Sendly does NOT send the confirmation email — subscribing returns a `confirm_token` and you deliver `/api/lists/confirm-subscription?token=<token>` to the contact yourself.",
            "type": "boolean"
          },
          "name": {
            "maxLength": 200,
            "minLength": 1,
            "type": "string"
          },
          "redirect_url": {
            "description": "Where a confirmed contact is sent after following the confirmation link.",
            "format": "uri",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "ListV1Deleted": {
        "description": "Acknowledgement that a list was deleted.",
        "properties": {
          "deleted": {
            "enum": [
              true
            ],
            "type": "boolean"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "id",
          "deleted"
        ],
        "type": "object"
      },
      "ListV1List": {
        "description": "Cursor-paginated list of subscriber lists.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/ListV1"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "description": "Pass as `after` to fetch the next page. `null` on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "type": "object"
      },
      "ListV1Update": {
        "description": "Body for PATCH /api/v1/lists/{id}.",
        "properties": {
          "confirmation_template_id": {
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "description": {
            "maxLength": 500,
            "type": [
              "string",
              "null"
            ]
          },
          "double_opt_in": {
            "type": "boolean"
          },
          "name": {
            "maxLength": 200,
            "minLength": 1,
            "type": "string"
          },
          "redirect_url": {
            "format": "uri",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "Mailbox": {
        "description": "A receiving mailbox on one of the project's verified domains.",
        "properties": {
          "address": {
            "description": "The full mailbox address, e.g. `support@superbooks.io`.",
            "format": "email",
            "type": "string"
          },
          "createdAt": {
            "format": "date-time",
            "type": "string"
          },
          "displayName": {
            "type": [
              "string",
              "null"
            ]
          },
          "domainId": {
            "description": "The verified domain this mailbox lives on.",
            "format": "uuid",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "quotaBytes": {
            "description": "Always null. Mailbox quotas are not implemented — the value was never applied to the mail account — so this field reports the absence rather than a number nothing enforces.",
            "type": [
              "number",
              "null"
            ]
          },
          "status": {
            "description": "`PROVISIONING` while the mail account is being created, `ACTIVE` once it can receive, `SUSPENDED` when receiving is paused, `FAILED` when provisioning did not complete. A `FAILED` mailbox can be re-created with the same address — the retry reclaims the row.",
            "enum": [
              "PROVISIONING",
              "ACTIVE",
              "SUSPENDED",
              "FAILED"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "address",
          "displayName",
          "status",
          "quotaBytes",
          "domainId",
          "createdAt"
        ],
        "type": "object"
      },
      "MailboxDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Mailbox"
          },
          {
            "properties": {
              "settings": {
                "description": "Host, port and username for connecting a mail client. The PASSWORD is not here and is never returned by this endpoint — create an app password for that.",
                "properties": {
                  "imap": {
                    "properties": {
                      "host": {
                        "type": "string"
                      },
                      "port": {
                        "type": "integer"
                      },
                      "security": {
                        "description": "Transport security, e.g. `SSL/TLS`.",
                        "type": "string"
                      },
                      "username": {
                        "description": "The mailbox address — it is also the login.",
                        "type": "string"
                      }
                    },
                    "required": [
                      "host",
                      "port",
                      "security",
                      "username"
                    ],
                    "type": "object"
                  },
                  "smtp": {
                    "properties": {
                      "host": {
                        "type": "string"
                      },
                      "port": {
                        "type": "integer"
                      },
                      "security": {
                        "description": "Transport security, e.g. `SSL/TLS`.",
                        "type": "string"
                      },
                      "username": {
                        "description": "The mailbox address — it is also the login.",
                        "type": "string"
                      }
                    },
                    "required": [
                      "host",
                      "port",
                      "security",
                      "username"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "imap",
                  "smtp"
                ],
                "type": "object"
              }
            },
            "required": [
              "settings"
            ],
            "type": "object"
          }
        ],
        "description": "A mailbox plus its IMAP/SMTP connection settings."
      },
      "Problem": {
        "description": "RFC 9457 problem document, served as `application/problem+json`. Returned by every 4xx/5xx response on the `/api/v1` surface.",
        "properties": {
          "code": {
            "description": "Machine-readable lowercase error code, e.g. `scope_missing`.",
            "type": "string"
          },
          "detail": {
            "description": "Explanation specific to this occurrence.",
            "type": "string"
          },
          "errors": {
            "description": "Field-level failures. Present on 422 `validation_error` responses.",
            "items": {
              "properties": {
                "code": {
                  "type": "string"
                },
                "message": {
                  "type": "string"
                },
                "pointer": {
                  "description": "RFC 6901 JSON Pointer to the offending field.",
                  "type": "string"
                }
              },
              "required": [
                "pointer",
                "code",
                "message"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "instance": {
            "description": "Request path the failure occurred on.",
            "type": "string"
          },
          "request_id": {
            "description": "Correlation id — quote it in support requests.",
            "type": "string"
          },
          "status": {
            "description": "HTTP status code, repeated in the body.",
            "type": "integer"
          },
          "title": {
            "description": "Short, stable summary — the same for every occurrence of a `type`.",
            "type": "string"
          },
          "type": {
            "description": "Dereferenceable URI identifying the error class, anchored on the docs errors page.",
            "format": "uri",
            "type": "string"
          }
        },
        "required": [
          "type",
          "title",
          "status",
          "code"
        ],
        "type": "object"
      },
      "ProjectRecord": {
        "properties": {
          "billingLimitCampaigns": {
            "type": [
              "integer",
              "null"
            ]
          },
          "billingLimitInbound": {
            "type": [
              "integer",
              "null"
            ]
          },
          "billingLimitTransactional": {
            "type": [
              "integer",
              "null"
            ]
          },
          "billingLimitWorkflows": {
            "type": [
              "integer",
              "null"
            ]
          },
          "createdAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          },
          "disabled": {
            "type": "boolean"
          },
          "disabledReason": {
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "language": {
            "description": "ISO 639-1 code for customer-facing content.",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "organizationId": {
            "type": [
              "string",
              "null"
            ]
          },
          "sandboxHandle": {
            "description": "Local-part of the sandbox quick-start sender; null until first derived.",
            "type": [
              "string",
              "null"
            ]
          },
          "sesRegion": {
            "type": [
              "string",
              "null"
            ]
          },
          "stripeCustomerId": {
            "type": [
              "string",
              "null"
            ]
          },
          "stripeSubscriptionId": {
            "type": [
              "string",
              "null"
            ]
          },
          "tracking": {
            "enum": [
              "ENABLED",
              "DISABLED",
              "MARKETING_ONLY"
            ],
            "type": "string"
          },
          "updatedAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "disabled",
          "disabledReason",
          "sandboxHandle",
          "stripeCustomerId",
          "stripeSubscriptionId",
          "billingLimitWorkflows",
          "billingLimitCampaigns",
          "billingLimitTransactional",
          "billingLimitInbound",
          "tracking",
          "sesRegion",
          "language",
          "organizationId",
          "createdAt",
          "updatedAt"
        ],
        "type": "object"
      },
      "ProjectV1": {
        "description": "The project the presented credential is scoped to.",
        "properties": {
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "disabled": {
            "description": "A disabled project sends nothing; every send is refused.",
            "type": "boolean"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "language": {
            "description": "ISO 639-1 code for customer-facing content.",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "sandbox_address": {
            "description": "This project's quick-start sender, usable with no domain setup — but only to the project owner's own verified address, and under a daily cap. Null when none can be derived.",
            "type": [
              "string",
              "null"
            ]
          },
          "ses_region": {
            "description": "Locked once the first domain is added.",
            "type": [
              "string",
              "null"
            ]
          },
          "tracking": {
            "enum": [
              "ENABLED",
              "DISABLED",
              "MARKETING_ONLY"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "disabled",
          "sandbox_address",
          "ses_region",
          "tracking",
          "language",
          "created_at"
        ],
        "type": "object"
      },
      "RecipientDomainStatsV1": {
        "description": "Delivery outcomes for one recipient domain on one day.",
        "properties": {
          "bounced": {
            "type": "integer"
          },
          "complained": {
            "type": "integer"
          },
          "computed_at": {
            "description": "When the rollup job last rebuilt this row. These counts are a CACHE, refreshed hourly.",
            "format": "date-time",
            "type": "string"
          },
          "day": {
            "description": "The UTC day these counts cover, as `YYYY-MM-DD`.",
            "type": "string"
          },
          "delivered": {
            "type": "integer"
          },
          "domain": {
            "description": "The recipient's domain, lowercased: the part after the `@`.",
            "type": "string"
          },
          "opened": {
            "type": "integer"
          },
          "sent": {
            "type": "integer"
          }
        },
        "required": [
          "domain",
          "day",
          "sent",
          "delivered",
          "bounced",
          "complained",
          "opened",
          "computed_at"
        ],
        "type": "object"
      },
      "RecipientDomainStatsV1List": {
        "description": "Cursor-paginated recipient-domain rollup, newest day first.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/RecipientDomainStatsV1"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "description": "Pass as `after` to fetch the next page. `null` on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "type": "object"
      },
      "SegmentContactV1": {
        "description": "A contact belonging to a segment.",
        "properties": {
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "custom_fields": {
            "additionalProperties": {},
            "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
            "type": "object"
          },
          "email": {
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "subscribed": {
            "type": "boolean"
          }
        },
        "required": [
          "id",
          "email",
          "subscribed",
          "custom_fields",
          "created_at"
        ],
        "type": "object"
      },
      "SegmentContactV1List": {
        "description": "Cursor-paginated list of the contacts belonging to a segment.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/SegmentContactV1"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "description": "Pass as `after` to fetch the next page. `null` on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "type": "object"
      },
      "SegmentFilterV1": {
        "description": "One comparison. `field` addresses a contact column (`email`, `createdAt`, …) or a `customFields.<key>` path. `value` is whatever the operator compares against — its JSON type follows the field, and it is omitted entirely for the presence operators (`exists`, `notExists`). `unit` applies only to the relative-time operators (`within`, `olderThan`, and the `triggered*` family).",
        "properties": {
          "field": {
            "minLength": 1,
            "type": "string"
          },
          "operator": {
            "enum": [
              "equals",
              "notEquals",
              "contains",
              "notContains",
              "greaterThan",
              "lessThan",
              "greaterThanOrEqual",
              "lessThanOrEqual",
              "exists",
              "notExists",
              "within",
              "olderThan",
              "triggered",
              "triggeredWithin",
              "triggeredOlderThan",
              "notTriggered",
              "notTriggeredWithin",
              "isMemberOf"
            ],
            "type": "string"
          },
          "unit": {
            "enum": [
              "days",
              "hours",
              "minutes"
            ],
            "type": "string"
          },
          "value": {}
        },
        "required": [
          "field",
          "operator"
        ],
        "type": "object"
      },
      "SegmentV1": {
        "description": "A segment as exposed on the v1 API.",
        "properties": {
          "condition": {
            "anyOf": [
              {
                "$ref": "#/components/schemas/FilterConditionV1"
              },
              {
                "type": "null"
              }
            ]
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "member_count": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "track_membership": {
            "type": "boolean"
          },
          "type": {
            "enum": [
              "DYNAMIC",
              "STATIC"
            ],
            "type": "string"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "type",
          "condition",
          "track_membership",
          "member_count",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "SegmentV1Create": {
        "description": "Body for POST /api/v1/segments. `condition` is required when `type` is `DYNAMIC`.",
        "properties": {
          "condition": {
            "$ref": "#/components/schemas/FilterConditionV1"
          },
          "description": {
            "maxLength": 500,
            "type": "string"
          },
          "name": {
            "maxLength": 100,
            "minLength": 1,
            "type": "string"
          },
          "track_membership": {
            "default": false,
            "description": "Emit segment entry/exit events as contacts move in and out. Off by default — it costs a membership write per transition.",
            "type": "boolean"
          },
          "type": {
            "default": "DYNAMIC",
            "enum": [
              "DYNAMIC",
              "STATIC"
            ],
            "type": "string"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "SegmentV1Deleted": {
        "description": "Acknowledgement that a segment was deleted.",
        "properties": {
          "deleted": {
            "enum": [
              true
            ],
            "type": "boolean"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "id",
          "deleted"
        ],
        "type": "object"
      },
      "SegmentV1List": {
        "description": "Cursor-paginated list of segments.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/SegmentV1"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "description": "Pass as `after` to fetch the next page. `null` on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "type": "object"
      },
      "SegmentV1Update": {
        "description": "Body for PATCH /api/v1/segments/{id}. `type` is deliberately absent — it is fixed at creation. `condition` is ignored on a STATIC segment.",
        "properties": {
          "condition": {
            "$ref": "#/components/schemas/FilterConditionV1"
          },
          "description": {
            "maxLength": 500,
            "type": "string"
          },
          "name": {
            "maxLength": 100,
            "minLength": 1,
            "type": "string"
          },
          "track_membership": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "SendEmail": {
        "description": "Body for POST /api/emails — send a single transactional email. Either `template` or `subject`+`body` is required.",
        "properties": {
          "attachments": {
            "items": {
              "properties": {
                "content": {
                  "minLength": 1,
                  "type": "string"
                },
                "contentId": {
                  "maxLength": 255,
                  "minLength": 1,
                  "pattern": "^[^<>\\r\\n]+$",
                  "type": "string"
                },
                "contentType": {
                  "maxLength": 255,
                  "minLength": 1,
                  "type": "string"
                },
                "disposition": {
                  "default": "attachment",
                  "enum": [
                    "attachment",
                    "inline"
                  ],
                  "type": "string"
                },
                "filename": {
                  "maxLength": 255,
                  "minLength": 1,
                  "pattern": "^[^\\r\\n\"]+$",
                  "type": "string"
                }
              },
              "required": [
                "filename",
                "content",
                "contentType"
              ],
              "type": "object"
            },
            "maxItems": 10,
            "type": "array"
          },
          "bcc": {
            "items": {
              "format": "email",
              "type": "string"
            },
            "type": "array"
          },
          "body": {
            "minLength": 1,
            "type": "string"
          },
          "cc": {
            "items": {
              "format": "email",
              "type": "string"
            },
            "type": "array"
          },
          "data": {
            "additionalProperties": {},
            "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
            "type": "object"
          },
          "from": {
            "anyOf": [
              {
                "format": "email",
                "type": "string"
              },
              {
                "properties": {
                  "email": {
                    "format": "email",
                    "type": "string"
                  },
                  "name": {
                    "pattern": "^[^\\r\\n]*$",
                    "type": "string"
                  }
                },
                "required": [
                  "email"
                ],
                "type": "object"
              }
            ]
          },
          "headers": {
            "additionalProperties": {
              "maxLength": 998,
              "pattern": "^[^\\r\\n]*$",
              "type": "string"
            },
            "type": "object"
          },
          "name": {
            "type": "string"
          },
          "reply": {
            "format": "email",
            "type": "string"
          },
          "subject": {
            "maxLength": 998,
            "minLength": 1,
            "pattern": "^[^\\r\\n]*$",
            "type": "string"
          },
          "subscribed": {
            "type": "boolean"
          },
          "tags": {
            "items": {
              "maxLength": 64,
              "minLength": 1,
              "pattern": "^[a-zA-Z0-9_-]+$",
              "type": "string"
            },
            "maxItems": 10,
            "type": "array"
          },
          "template": {
            "format": "uuid",
            "type": "string"
          },
          "to": {
            "anyOf": [
              {
                "format": "email",
                "type": "string"
              },
              {
                "properties": {
                  "email": {
                    "format": "email",
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "email"
                ],
                "type": "object"
              },
              {
                "items": {
                  "anyOf": [
                    {
                      "format": "email",
                      "type": "string"
                    },
                    {
                      "properties": {
                        "email": {
                          "format": "email",
                          "type": "string"
                        },
                        "name": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "email"
                      ],
                      "type": "object"
                    }
                  ]
                },
                "type": "array"
              }
            ]
          }
        },
        "required": [
          "to"
        ],
        "type": "object"
      },
      "SendEmailData": {
        "description": "Result of a send (`executeSendEmail`): one `emails` entry per recipient — a single request with an array `to` fans out to several — plus the send `timestamp`.",
        "properties": {
          "emails": {
            "items": {
              "$ref": "#/components/schemas/SendEmailRecipientResult"
            },
            "type": "array"
          },
          "timestamp": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "emails",
          "timestamp"
        ],
        "type": "object"
      },
      "SendEmailRecipientResult": {
        "description": "Per-recipient result: the upserted `contact` (id + email) and `email` — the id of the queued email record for that recipient.",
        "properties": {
          "contact": {
            "properties": {
              "email": {
                "format": "email",
                "type": "string"
              },
              "id": {
                "format": "uuid",
                "type": "string"
              }
            },
            "required": [
              "id",
              "email"
            ],
            "type": "object"
          },
          "email": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "contact",
          "email"
        ],
        "type": "object"
      },
      "SendEmailResponse": {
        "description": "Successful response for `POST /api/emails`. `data.emails[i].email` is the queued email id for recipient `i`; poll `GET /api/emails/{id}` for its delivery status.",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/SendEmailData"
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "SendEmailV1": {
        "description": "Body for POST /api/v1/emails. Either `template` or `subject`+`body` is required, and `to` names exactly one recipient.",
        "properties": {
          "attachments": {
            "items": {
              "properties": {
                "content": {
                  "minLength": 1,
                  "type": "string"
                },
                "contentId": {
                  "maxLength": 255,
                  "minLength": 1,
                  "pattern": "^[^<>\\r\\n]+$",
                  "type": "string"
                },
                "contentType": {
                  "maxLength": 255,
                  "minLength": 1,
                  "type": "string"
                },
                "disposition": {
                  "default": "attachment",
                  "enum": [
                    "attachment",
                    "inline"
                  ],
                  "type": "string"
                },
                "filename": {
                  "maxLength": 255,
                  "minLength": 1,
                  "pattern": "^[^\\r\\n\"]+$",
                  "type": "string"
                }
              },
              "required": [
                "filename",
                "content",
                "contentType"
              ],
              "type": "object"
            },
            "maxItems": 10,
            "type": "array"
          },
          "bcc": {
            "items": {
              "format": "email",
              "type": "string"
            },
            "type": "array"
          },
          "body": {
            "minLength": 1,
            "type": "string"
          },
          "cc": {
            "items": {
              "format": "email",
              "type": "string"
            },
            "type": "array"
          },
          "data": {
            "additionalProperties": {},
            "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
            "type": "object"
          },
          "from": {
            "anyOf": [
              {
                "format": "email",
                "type": "string"
              },
              {
                "properties": {
                  "email": {
                    "format": "email",
                    "type": "string"
                  },
                  "name": {
                    "pattern": "^[^\\r\\n]*$",
                    "type": "string"
                  }
                },
                "required": [
                  "email"
                ],
                "type": "object"
              }
            ]
          },
          "headers": {
            "additionalProperties": {
              "maxLength": 998,
              "pattern": "^[^\\r\\n]*$",
              "type": "string"
            },
            "type": "object"
          },
          "name": {
            "type": "string"
          },
          "reply": {
            "format": "email",
            "type": "string"
          },
          "subject": {
            "maxLength": 998,
            "minLength": 1,
            "pattern": "^[^\\r\\n]*$",
            "type": "string"
          },
          "subscribed": {
            "type": "boolean"
          },
          "tags": {
            "items": {
              "maxLength": 64,
              "minLength": 1,
              "pattern": "^[a-zA-Z0-9_-]+$",
              "type": "string"
            },
            "maxItems": 10,
            "type": "array"
          },
          "template": {
            "format": "uuid",
            "type": "string"
          },
          "to": {
            "anyOf": [
              {
                "format": "email",
                "type": "string"
              },
              {
                "properties": {
                  "email": {
                    "format": "email",
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  }
                },
                "required": [
                  "email"
                ],
                "type": "object"
              }
            ],
            "description": "The single recipient. Use `cc`/`bcc` to copy others on the same message."
          }
        },
        "required": [
          "to"
        ],
        "type": "object"
      },
      "SendTestEmailV1": {
        "description": "Body for POST /api/v1/emails/test. `subject` and `body` are required; `to` defaults to the project owner's verified email, and `from` is refused.",
        "properties": {
          "body": {
            "description": "HTML body. Merge tags are rendered as on any other send.",
            "minLength": 1,
            "type": "string"
          },
          "from": {
            "description": "NOT ACCEPTED. The sender is always this project's sandbox address, resolved server-side; naming one here is refused rather than ignored, so a request that expects a different sender never gets a success it would misread. Read `sandbox_address` from `GET /api/v1/projects` to learn the address, or `from` off this response.",
            "type": "string"
          },
          "subject": {
            "maxLength": 998,
            "minLength": 1,
            "type": "string"
          },
          "to": {
            "description": "Where to send it. Optional — it defaults to the project owner's own verified account email, which is the only address a sandbox send may reach. Any other value is refused.",
            "format": "email",
            "type": "string"
          }
        },
        "required": [
          "subject",
          "body"
        ],
        "type": "object"
      },
      "SendingStream": {
        "description": "Which traffic this identity carries. Omit to leave it unassigned, which lets it carry every stream — that is what every domain added before per-stream identities does.",
        "enum": [
          "TRANSACTIONAL",
          "MARKETING"
        ],
        "type": "string"
      },
      "Snippet": {
        "description": "A reusable fragment of template markup.",
        "properties": {
          "body": {
            "description": "Template markup. Values it interpolates are escaped like any other.",
            "type": "string"
          },
          "createdAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "description": "The literal identifier a template includes with `{{> name}}`.",
            "type": "string"
          },
          "projectId": {
            "format": "uuid",
            "type": "string"
          },
          "updatedAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "projectId",
          "name",
          "body",
          "createdAt",
          "updatedAt"
        ],
        "type": "object"
      },
      "SnippetListResponse": {
        "description": "Cursor-paginated list of snippets.",
        "properties": {
          "data": {
            "properties": {
              "cursor": {
                "description": "Cursor for the next page; omitted on the last page.",
                "type": "string"
              },
              "data": {
                "items": {
                  "$ref": "#/components/schemas/Snippet"
                },
                "type": "array"
              },
              "hasMore": {
                "type": "boolean"
              },
              "total": {
                "type": "integer"
              }
            },
            "required": [
              "data",
              "total",
              "hasMore"
            ],
            "type": "object"
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "SuccessEmpty": {
        "description": "Bare success envelope with no payload.",
        "properties": {
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success"
        ],
        "type": "object"
      },
      "Suppression": {
        "description": "A single suppressed-email record.",
        "properties": {
          "createdAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          },
          "email": {
            "format": "email",
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "projectId": {
            "format": "uuid",
            "type": "string"
          },
          "reason": {
            "enum": [
              "HARD_BOUNCE",
              "COMPLAINT",
              "MANUAL",
              "UNSUBSCRIBE"
            ],
            "type": "string"
          },
          "scope": {
            "description": "How far the suppression reaches. `PROJECT` is every record this API creates or returns today.",
            "enum": [
              "PROJECT",
              "GLOBAL"
            ],
            "type": "string"
          },
          "source": {
            "enum": [
              "SES_WEBHOOK",
              "API",
              "DASHBOARD"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "projectId",
          "email",
          "reason",
          "source",
          "scope",
          "createdAt"
        ],
        "type": "object"
      },
      "SuppressionCheckResponse": {
        "description": "Result of GET /api/suppression/{email} — whether the address is suppressed.",
        "properties": {
          "createdAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          },
          "reason": {
            "enum": [
              "HARD_BOUNCE",
              "COMPLAINT",
              "MANUAL",
              "UNSUBSCRIBE"
            ],
            "type": "string"
          },
          "source": {
            "enum": [
              "SES_WEBHOOK",
              "API",
              "DASHBOARD"
            ],
            "type": "string"
          },
          "suppressed": {
            "type": "boolean"
          }
        },
        "required": [
          "suppressed"
        ],
        "type": "object"
      },
      "SuppressionListResponse": {
        "description": "Cursor-paginated list of suppressions. NOTE: this route answers a bare body — there is no `{success, data}` envelope.",
        "properties": {
          "items": {
            "items": {
              "$ref": "#/components/schemas/Suppression"
            },
            "type": "array"
          },
          "nextCursor": {
            "description": "Cursor for the next page, or `null` on the last page. Never omitted.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "items",
          "nextCursor"
        ],
        "type": "object"
      },
      "SuppressionV1": {
        "description": "A suppressed address as exposed on the v1 API.",
        "properties": {
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "reason": {
            "enum": [
              "HARD_BOUNCE",
              "COMPLAINT",
              "MANUAL",
              "UNSUBSCRIBE"
            ],
            "type": "string"
          },
          "source": {
            "type": "string"
          }
        },
        "required": [
          "email",
          "reason",
          "source",
          "created_at"
        ],
        "type": "object"
      },
      "SuppressionV1Create": {
        "description": "Body for POST /api/v1/suppressions.",
        "properties": {
          "email": {
            "format": "email",
            "type": "string"
          },
          "reason": {
            "default": "MANUAL",
            "enum": [
              "HARD_BOUNCE",
              "COMPLAINT",
              "MANUAL",
              "UNSUBSCRIBE"
            ],
            "type": "string"
          }
        },
        "required": [
          "email"
        ],
        "type": "object"
      },
      "SuppressionV1Deleted": {
        "description": "Acknowledgement that an address was un-suppressed.",
        "properties": {
          "deleted": {
            "enum": [
              true
            ],
            "type": "boolean"
          },
          "email": {
            "type": "string"
          }
        },
        "required": [
          "email",
          "deleted"
        ],
        "type": "object"
      },
      "SuppressionV1List": {
        "description": "Cursor-paginated list of suppressed addresses.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/SuppressionV1"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "description": "Pass as `after` to fetch the next page. `null` on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "type": "object"
      },
      "Template": {
        "description": "A reusable email template.",
        "properties": {
          "body": {
            "type": "string"
          },
          "createdAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          },
          "currentVersion": {
            "description": "Version counter, incremented by an update that changes the rendered content. A campaign records the version it sent, so this is how a caller tells 'the template changed since' from 'the template was renamed'.",
            "type": "integer"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "emailCategory": {
            "enum": [
              "MARKETING",
              "TRANSACTIONAL",
              "SELF_MANAGED_UNSUBSCRIBE"
            ],
            "type": "string"
          },
          "from": {
            "format": "email",
            "type": "string"
          },
          "fromName": {
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "projectId": {
            "format": "uuid",
            "type": "string"
          },
          "replyTo": {
            "format": "email",
            "type": [
              "string",
              "null"
            ]
          },
          "subject": {
            "type": "string"
          },
          "updatedAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          }
        },
        "required": [
          "id",
          "projectId",
          "name",
          "subject",
          "body",
          "from",
          "emailCategory",
          "currentVersion",
          "createdAt",
          "updatedAt"
        ],
        "type": "object"
      },
      "TemplateListResponse": {
        "description": "Cursor-paginated list of templates.",
        "properties": {
          "data": {
            "properties": {
              "cursor": {
                "description": "Cursor for the next page; omitted on the last page.",
                "type": "string"
              },
              "data": {
                "items": {
                  "$ref": "#/components/schemas/Template"
                },
                "type": "array"
              },
              "hasMore": {
                "type": "boolean"
              },
              "total": {
                "type": "integer"
              }
            },
            "required": [
              "data",
              "total",
              "hasMore"
            ],
            "type": "object"
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "TemplateV1": {
        "description": "An email template as exposed on the v1 API.",
        "properties": {
          "body": {
            "type": "string"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "email_category": {
            "enum": [
              "TRANSACTIONAL",
              "MARKETING",
              "SELF_MANAGED_UNSUBSCRIBE"
            ],
            "type": "string"
          },
          "from": {
            "type": "string"
          },
          "from_name": {
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "reply_to": {
            "type": [
              "string",
              "null"
            ]
          },
          "subject": {
            "type": "string"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string"
          },
          "version": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "subject",
          "body",
          "from",
          "from_name",
          "reply_to",
          "email_category",
          "version",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "TemplateV1Create": {
        "description": "Body for POST /api/v1/templates.",
        "properties": {
          "body": {
            "minLength": 1,
            "type": "string"
          },
          "description": {
            "maxLength": 500,
            "type": [
              "string",
              "null"
            ]
          },
          "email_category": {
            "default": "MARKETING",
            "enum": [
              "TRANSACTIONAL",
              "MARKETING",
              "SELF_MANAGED_UNSUBSCRIBE"
            ],
            "type": "string"
          },
          "from": {
            "description": "Sender address. Its domain must be verified for this project.",
            "format": "email",
            "type": "string"
          },
          "from_name": {
            "maxLength": 100,
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "maxLength": 100,
            "minLength": 1,
            "type": "string"
          },
          "reply_to": {
            "format": "email",
            "type": [
              "string",
              "null"
            ]
          },
          "subject": {
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "name",
          "subject",
          "body",
          "from"
        ],
        "type": "object"
      },
      "TemplateV1Deleted": {
        "description": "Acknowledgement that a template was deleted.",
        "properties": {
          "deleted": {
            "enum": [
              true
            ],
            "type": "boolean"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "id",
          "deleted"
        ],
        "type": "object"
      },
      "TemplateV1List": {
        "description": "Cursor-paginated list of templates.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/TemplateV1"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "description": "Pass as `after` to fetch the next page. `null` on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "type": "object"
      },
      "TemplateV1Update": {
        "description": "Body for PATCH /api/v1/templates/{id}.",
        "properties": {
          "body": {
            "minLength": 1,
            "type": "string"
          },
          "description": {
            "maxLength": 500,
            "type": [
              "string",
              "null"
            ]
          },
          "email_category": {
            "enum": [
              "TRANSACTIONAL",
              "MARKETING",
              "SELF_MANAGED_UNSUBSCRIBE"
            ],
            "type": "string"
          },
          "from": {
            "format": "email",
            "type": "string"
          },
          "from_name": {
            "maxLength": 100,
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "maxLength": 100,
            "minLength": 1,
            "type": "string"
          },
          "reply_to": {
            "format": "email",
            "type": [
              "string",
              "null"
            ]
          },
          "subject": {
            "minLength": 1,
            "type": "string"
          }
        },
        "type": "object"
      },
      "TopicCreateV1": {
        "properties": {
          "default_opt_in": {
            "type": "boolean"
          },
          "description": {
            "maxLength": 1000,
            "type": [
              "string",
              "null"
            ]
          },
          "key": {
            "description": "Stable, URL-safe, unique within the project. This is what a preference form and an API caller name the topic by, so it must survive a rename of `name` — which is the whole reason it exists beside one.",
            "maxLength": 64,
            "minLength": 1,
            "pattern": "^[a-z0-9][a-z0-9_-]*$",
            "type": "string"
          },
          "name": {
            "maxLength": 200,
            "minLength": 1,
            "type": "string"
          }
        },
        "required": [
          "key",
          "name"
        ],
        "type": "object"
      },
      "TopicListV1": {
        "description": "One page of the subjects this project mails about.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/TopicV1"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "description": "Pass as `after` to fetch the next page. `null` on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "type": "object"
      },
      "TopicSubscribeV1": {
        "properties": {
          "contact_id": {
            "format": "uuid",
            "type": "string"
          },
          "subscribed": {
            "description": "True asks to subscribe, which starts a DOUBLE OPT-IN: the contact is parked at `pending` and a confirmation link is sent. There is no way to skip that from the API — a subscription an API caller asserts is not evidence the mailbox holder agreed, and treating it as consent is exactly what double opt-in exists to stop. False records an unsubscribe, which takes effect immediately.",
            "type": "boolean"
          }
        },
        "required": [
          "contact_id",
          "subscribed"
        ],
        "type": "object"
      },
      "TopicSubscriptionStatusV1": {
        "description": "`subscribed`: mail them about this. `unsubscribed`: do not. `pending`: a confirmation link was sent and has NOT been clicked — never treat this as consent, which is the whole point of double opt-in.",
        "enum": [
          "pending",
          "subscribed",
          "unsubscribed"
        ],
        "type": "string"
      },
      "TopicSubscriptionV1": {
        "properties": {
          "confirmation_url": {
            "description": "Present only when this call started a double opt-in. Sendly does NOT send the confirmation email — you do, from your own verified domain, because it is your relationship with the contact and your sending reputation. The subscription stays `pending`, and is NOT mailed, until someone opens this link.",
            "type": [
              "string",
              "null"
            ]
          },
          "confirmed_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "contact_id": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/TopicSubscriptionStatusV1"
          },
          "topic_id": {
            "type": "string"
          }
        },
        "required": [
          "topic_id",
          "contact_id",
          "status",
          "confirmed_at",
          "confirmation_url"
        ],
        "type": "object"
      },
      "TopicUpdateV1": {
        "description": "`key` is deliberately absent. It is the name every stored preference and every integration refers to, so changing it would silently orphan them.",
        "properties": {
          "archived": {
            "type": "boolean"
          },
          "default_opt_in": {
            "type": "boolean"
          },
          "description": {
            "maxLength": 1000,
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "maxLength": 200,
            "minLength": 1,
            "type": "string"
          }
        },
        "type": "object"
      },
      "TopicV1": {
        "description": "One subject this project mails about.",
        "properties": {
          "archived": {
            "description": "Hidden from the preference centre and from new sends, WITHOUT discarding the opt-outs recorded against it. There is no delete for the same reason: deleting a topic would delete the choices people made about it.",
            "type": "boolean"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "default_opt_in": {
            "description": "What a contact with NO answer counts as. True is the honest default for a topic added after a list already exists: those contacts consented to hear from you, and inventing an opt-out they never asked for would silence mail they expect. False means the topic must be opted INTO, and absence means `not asked` rather than `no`.",
            "type": "boolean"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "type": "string"
          },
          "key": {
            "description": "Stable, URL-safe, unique within the project. This is what a preference form and an API caller name the topic by, so it must survive a rename of `name` — which is the whole reason it exists beside one.",
            "maxLength": 64,
            "minLength": 1,
            "pattern": "^[a-z0-9][a-z0-9_-]*$",
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "subscribed_count": {
            "description": "Contacts who explicitly said yes. Excludes those covered only by `default_opt_in`.",
            "type": "integer"
          },
          "unsubscribed_count": {
            "type": "integer"
          }
        },
        "required": [
          "id",
          "key",
          "name",
          "description",
          "default_opt_in",
          "archived",
          "subscribed_count",
          "unsubscribed_count",
          "created_at"
        ],
        "type": "object"
      },
      "TrackEvent": {
        "description": "Body for POST /api/track — record a custom event for a contact.",
        "properties": {
          "data": {
            "additionalProperties": {},
            "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
            "type": "object"
          },
          "email": {
            "format": "email",
            "type": "string"
          },
          "event": {
            "minLength": 1,
            "type": "string"
          },
          "subscribed": {
            "type": "boolean"
          }
        },
        "required": [
          "event",
          "email"
        ],
        "type": "object"
      },
      "TrackEventResponse": {
        "description": "Response from POST /api/track.",
        "properties": {
          "data": {
            "properties": {
              "contact": {
                "format": "uuid",
                "type": "string"
              },
              "event": {
                "format": "uuid",
                "type": "string"
              },
              "timestamp": {
                "description": "ISO 8601 datetime string",
                "format": "date-time",
                "type": "string"
              }
            },
            "required": [
              "contact",
              "event",
              "timestamp"
            ],
            "type": "object"
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "UpdateContactBody": {
        "description": "Body for PATCH /api/contacts/{id}. `email` is immutable here — use upsert to change addresses.",
        "properties": {
          "customFields": {
            "additionalProperties": {},
            "type": "object"
          },
          "subscribed": {
            "type": "boolean"
          }
        },
        "type": "object"
      },
      "UpdateSnippet": {
        "description": "Body for PATCH /api/snippets/{id}.",
        "properties": {
          "body": {
            "maxLength": 20000,
            "minLength": 1,
            "type": "string"
          },
          "description": {
            "maxLength": 500,
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "pattern": "^[a-z][\\da-z_-]{0,63}$/i",
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpdateTemplate": {
        "description": "Body for PATCH /api/templates/{id}.",
        "properties": {
          "body": {
            "minLength": 1,
            "type": "string"
          },
          "description": {
            "maxLength": 500,
            "type": "string"
          },
          "emailCategory": {
            "enum": [
              "TRANSACTIONAL",
              "MARKETING",
              "SELF_MANAGED_UNSUBSCRIBE"
            ],
            "type": "string"
          },
          "from": {
            "format": "email",
            "type": "string"
          },
          "fromName": {
            "maxLength": 100,
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "maxLength": 100,
            "minLength": 1,
            "type": "string"
          },
          "replyTo": {
            "format": "email",
            "type": [
              "string",
              "null"
            ]
          },
          "subject": {
            "minLength": 1,
            "type": "string"
          }
        },
        "type": "object"
      },
      "UpdateWebhook": {
        "description": "Body for PATCH /api/webhooks/{id}.",
        "properties": {
          "eventTypes": {
            "items": {
              "enum": [
                "email.sent",
                "email.delivered",
                "email.opened",
                "email.clicked",
                "email.bounced",
                "email.complained",
                "email.failed",
                "contact.created",
                "contact.unsubscribed",
                "contacts.bulk_created"
              ],
              "type": "string"
            },
            "minItems": 1,
            "type": "array"
          },
          "status": {
            "enum": [
              "ACTIVE",
              "PAUSED",
              "DISABLED"
            ],
            "type": "string"
          },
          "url": {
            "format": "uri",
            "type": "string"
          }
        },
        "type": "object"
      },
      "UsageV1": {
        "description": "Current email usage against the limits that are actually enforced.",
        "properties": {
          "daily": {
            "properties": {
              "emails_sent": {
                "description": "Today's sends. Null when the counter could not be read.",
                "type": [
                  "integer",
                  "null"
                ]
              },
              "limit": {
                "type": "integer"
              },
              "trust_tier": {
                "enum": [
                  "NEW",
                  "ESTABLISHED",
                  "TRUSTED"
                ],
                "type": "string"
              }
            },
            "required": [
              "emails_sent",
              "limit",
              "trust_tier"
            ],
            "type": "object"
          },
          "monthly": {
            "properties": {
              "categories": {
                "properties": {
                  "campaign": {
                    "properties": {
                      "emails_sent": {
                        "type": "integer"
                      },
                      "limit": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      }
                    },
                    "required": [
                      "emails_sent",
                      "limit"
                    ],
                    "type": "object"
                  },
                  "inbound": {
                    "properties": {
                      "emails_sent": {
                        "type": "integer"
                      },
                      "limit": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      }
                    },
                    "required": [
                      "emails_sent",
                      "limit"
                    ],
                    "type": "object"
                  },
                  "transactional": {
                    "properties": {
                      "emails_sent": {
                        "type": "integer"
                      },
                      "limit": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      }
                    },
                    "required": [
                      "emails_sent",
                      "limit"
                    ],
                    "type": "object"
                  },
                  "workflow": {
                    "properties": {
                      "emails_sent": {
                        "type": "integer"
                      },
                      "limit": {
                        "type": [
                          "integer",
                          "null"
                        ]
                      }
                    },
                    "required": [
                      "emails_sent",
                      "limit"
                    ],
                    "type": "object"
                  }
                },
                "required": [
                  "transactional",
                  "campaign",
                  "workflow",
                  "inbound"
                ],
                "type": "object"
              },
              "emails_sent": {
                "type": "integer"
              },
              "limit": {
                "description": "Monthly cap on the total. Null when per-category limits govern instead.",
                "type": [
                  "integer",
                  "null"
                ]
              }
            },
            "required": [
              "emails_sent",
              "limit",
              "categories"
            ],
            "type": "object"
          },
          "plan": {
            "description": "`custom` when an operator set per-category limits, `pro` on an active subscription or store entitlement, else `free`.",
            "enum": [
              "free",
              "pro",
              "custom"
            ],
            "type": "string"
          }
        },
        "required": [
          "plan",
          "monthly",
          "daily"
        ],
        "type": "object"
      },
      "VerifyEmail": {
        "description": "Body for POST /api/verify — validate email syntax, MX, disposable, etc.",
        "properties": {
          "email": {
            "format": "email",
            "type": "string"
          }
        },
        "required": [
          "email"
        ],
        "type": "object"
      },
      "VerifyEmailResponse": {
        "description": "Response from POST /api/verify — outcome of the syntax/MX/disposable check.",
        "properties": {
          "data": {
            "additionalProperties": {},
            "properties": {
              "email": {
                "format": "email",
                "type": "string"
              },
              "reason": {
                "type": "string"
              },
              "valid": {
                "type": "boolean"
              }
            },
            "required": [
              "email",
              "valid"
            ],
            "type": "object"
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "Webhook": {
        "description": "A user-managed outbound webhook. Never carries a secret.",
        "properties": {
          "consecutiveFailures": {
            "type": "integer"
          },
          "createdAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          },
          "disabledAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "domains": {
            "description": "Sending domains this endpoint is scoped to. Empty means every domain on the project.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "eventTypes": {
            "items": {
              "enum": [
                "email.sent",
                "email.delivered",
                "email.opened",
                "email.clicked",
                "email.bounced",
                "email.complained",
                "email.failed",
                "contact.created",
                "contact.unsubscribed",
                "contacts.bulk_created"
              ],
              "type": "string"
            },
            "type": "array"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "previousSecretExpiresAt": {
            "description": "While a rotation is in flight, when the OLD secret stops being accepted. `null` outside a rotation.",
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "projectId": {
            "format": "uuid",
            "type": "string"
          },
          "status": {
            "enum": [
              "ACTIVE",
              "PAUSED",
              "DISABLED"
            ],
            "type": "string"
          },
          "updatedAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          },
          "url": {
            "format": "uri",
            "type": "string"
          }
        },
        "required": [
          "id",
          "projectId",
          "url",
          "eventTypes",
          "status",
          "domains",
          "consecutiveFailures",
          "createdAt",
          "updatedAt"
        ],
        "type": "object"
      },
      "WebhookCall": {
        "description": "An attempted webhook delivery.",
        "properties": {
          "attempt": {
            "type": "integer"
          },
          "createdAt": {
            "description": "ISO 8601 datetime string",
            "format": "date-time",
            "type": "string"
          },
          "eventType": {
            "type": "string"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "payload": {
            "additionalProperties": {},
            "type": "object"
          },
          "responseBody": {
            "type": [
              "string",
              "null"
            ]
          },
          "responseStatus": {
            "type": [
              "integer",
              "null"
            ]
          },
          "status": {
            "enum": [
              "PENDING",
              "SUCCESS",
              "FAILED"
            ],
            "type": "string"
          },
          "webhookId": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "id",
          "webhookId",
          "eventType",
          "payload",
          "status",
          "attempt",
          "createdAt"
        ],
        "type": "object"
      },
      "WebhookCallsListResponse": {
        "description": "Cursor-paginated list of recent calls for a single webhook.",
        "properties": {
          "cursor": {
            "type": [
              "string",
              "null"
            ]
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/WebhookCall"
            },
            "type": "array"
          },
          "hasMore": {
            "type": "boolean"
          },
          "nextCursor": {
            "type": [
              "string",
              "null"
            ]
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "WebhookCreateResponse": {
        "description": "Result of POST /api/webhooks. `secret` is the only time the plaintext is returned — store it securely.",
        "properties": {
          "data": {
            "properties": {
              "secret": {
                "description": "Plaintext shared secret. Returned ONCE on create.",
                "type": "string"
              },
              "webhook": {
                "$ref": "#/components/schemas/Webhook"
              }
            },
            "required": [
              "webhook",
              "secret"
            ],
            "type": "object"
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "WebhookGetResponse": {
        "description": "Single webhook (no secret).",
        "properties": {
          "data": {
            "$ref": "#/components/schemas/Webhook"
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "WebhookListResponse": {
        "description": "List of webhooks for the auth'd project.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/Webhook"
            },
            "type": "array"
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "WebhookRotateSecretResponse": {
        "description": "Response from POST /api/webhooks/{id}/rotate-secret — returns the new plaintext once.",
        "properties": {
          "data": {
            "properties": {
              "id": {
                "format": "uuid",
                "type": "string"
              },
              "secret": {
                "description": "New plaintext shared secret.",
                "type": "string"
              }
            },
            "required": [
              "id",
              "secret"
            ],
            "type": "object"
          },
          "success": {
            "enum": [
              true
            ],
            "type": "boolean"
          }
        },
        "required": [
          "success",
          "data"
        ],
        "type": "object"
      },
      "WebhookV1": {
        "description": "A webhook endpoint as exposed on the v1 API. The signing secret is NEVER on this shape — it is returned once, by create and by rotate, and no endpoint reads it back.",
        "properties": {
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "event_types": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "status": {
            "enum": [
              "ACTIVE",
              "PAUSED",
              "DISABLED"
            ],
            "type": "string"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string"
          },
          "url": {
            "type": "string"
          }
        },
        "required": [
          "id",
          "url",
          "event_types",
          "status",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "WebhookV1Create": {
        "description": "Body for POST /api/v1/webhooks.",
        "properties": {
          "event_types": {
            "items": {
              "enum": [
                "email.sent",
                "email.delivered",
                "email.opened",
                "email.clicked",
                "email.bounced",
                "email.complained",
                "email.failed",
                "contact.created",
                "contact.unsubscribed",
                "contacts.bulk_created"
              ],
              "type": "string"
            },
            "minItems": 1,
            "type": "array"
          },
          "url": {
            "format": "uri",
            "type": "string"
          }
        },
        "required": [
          "url",
          "event_types"
        ],
        "type": "object"
      },
      "WebhookV1Created": {
        "description": "A newly created webhook and its one-time signing secret.",
        "properties": {
          "secret": {
            "description": "The signing secret, shown EXACTLY ONCE. Store it now — no endpoint returns it again.",
            "type": "string"
          },
          "webhook": {
            "$ref": "#/components/schemas/WebhookV1"
          }
        },
        "required": [
          "webhook",
          "secret"
        ],
        "type": "object"
      },
      "WebhookV1Deleted": {
        "description": "Acknowledgement that a webhook was deleted.",
        "properties": {
          "deleted": {
            "enum": [
              true
            ],
            "type": "boolean"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "id",
          "deleted"
        ],
        "type": "object"
      },
      "WebhookV1List": {
        "description": "Cursor-paginated list of webhook endpoints.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/WebhookV1"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "description": "Pass as `after` to fetch the next page. `null` on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "type": "object"
      },
      "WebhookV1SecretRotated": {
        "description": "A freshly rotated signing secret, and the moment the outgoing one stops verifying.",
        "properties": {
          "previous_secret_expires_at": {
            "description": "When the PREVIOUS secret stops verifying. Until then every delivery carries both signatures, so a consumer can redeploy its verifier without dropping an event.",
            "format": "date-time",
            "type": "string"
          },
          "secret": {
            "description": "The new signing secret, shown EXACTLY ONCE. Store it now — no endpoint returns it again.",
            "type": "string"
          }
        },
        "required": [
          "secret",
          "previous_secret_expires_at"
        ],
        "type": "object"
      },
      "WebhookV1Update": {
        "description": "Body for PATCH /api/v1/webhooks/{id}.",
        "properties": {
          "event_types": {
            "items": {
              "enum": [
                "email.sent",
                "email.delivered",
                "email.opened",
                "email.clicked",
                "email.bounced",
                "email.complained",
                "email.failed",
                "contact.created",
                "contact.unsubscribed",
                "contacts.bulk_created"
              ],
              "type": "string"
            },
            "minItems": 1,
            "type": "array"
          },
          "status": {
            "enum": [
              "ACTIVE",
              "PAUSED",
              "DISABLED"
            ],
            "type": "string"
          },
          "url": {
            "format": "uri",
            "type": "string"
          }
        },
        "type": "object"
      },
      "WorkflowCloneV1": {
        "description": "Body for `POST /api/v1/workflows/{id}/clone`.",
        "properties": {
          "name": {
            "description": "Name for the copy. Defaults to `Copy of <original name>`.",
            "maxLength": 200,
            "minLength": 1,
            "type": "string"
          }
        },
        "type": "object"
      },
      "WorkflowConditionStepV1": {
        "description": "Branches the run. Binary form: `field` + `operator` + `value`, whose outgoing transitions carry `{ \"branch\": \"yes\" }` / `{ \"branch\": \"no\" }`. Multi form: `mode: \"multi\"` + `field` + `branches`, whose transitions carry the branch id.",
        "properties": {
          "config": {
            "additionalProperties": {
              "additionalProperties": {},
              "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
              "type": [
                "string",
                "number",
                "boolean",
                "object",
                "array",
                "null"
              ]
            },
            "properties": {
              "branches": {
                "items": {
                  "additionalProperties": {
                    "additionalProperties": {},
                    "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
                    "type": [
                      "string",
                      "number",
                      "boolean",
                      "object",
                      "array",
                      "null"
                    ]
                  },
                  "properties": {
                    "id": {
                      "minLength": 1,
                      "type": "string"
                    },
                    "name": {
                      "minLength": 1,
                      "type": "string"
                    },
                    "operator": {
                      "enum": [
                        "equals",
                        "notEquals",
                        "contains",
                        "notContains",
                        "greaterThan",
                        "lessThan",
                        "greaterThanOrEqual",
                        "lessThanOrEqual",
                        "exists",
                        "notExists"
                      ],
                      "type": "string"
                    },
                    "value": {
                      "additionalProperties": {},
                      "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
                      "type": [
                        "string",
                        "number",
                        "boolean",
                        "object",
                        "array",
                        "null"
                      ]
                    }
                  },
                  "required": [
                    "id",
                    "name",
                    "operator"
                  ],
                  "type": "object"
                },
                "maxItems": 20,
                "type": "array"
              },
              "field": {
                "minLength": 1,
                "type": "string"
              },
              "mode": {
                "enum": [
                  "multi"
                ],
                "type": "string"
              },
              "operator": {
                "enum": [
                  "equals",
                  "notEquals",
                  "contains",
                  "notContains",
                  "greaterThan",
                  "lessThan",
                  "greaterThanOrEqual",
                  "lessThanOrEqual",
                  "exists",
                  "notExists"
                ],
                "type": "string"
              },
              "value": {
                "additionalProperties": {},
                "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
                "type": [
                  "string",
                  "number",
                  "boolean",
                  "object",
                  "array",
                  "null"
                ]
              }
            },
            "type": "object"
          },
          "id": {
            "description": "Stable step id. On a graph write it is CHOSEN BY THE CALLER: send back the id you read to keep a step (and its run history), a fresh uuid to add one, and omit an id to delete that step.",
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "maxLength": 100,
            "minLength": 1,
            "type": "string"
          },
          "position": {
            "$ref": "#/components/schemas/WorkflowStepPositionV1"
          },
          "template_id": {
            "description": "The `SEND_EMAIL`/`SEND_AT_OPTIMAL_TIME` template, as a relation. Null for every other step type.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "enum": [
              "CONDITION"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "position",
          "type",
          "config"
        ],
        "type": "object"
      },
      "WorkflowCreateV1": {
        "description": "Body for POST /api/v1/workflows.",
        "properties": {
          "allow_reentry": {
            "type": "boolean"
          },
          "description": {
            "maxLength": 1000,
            "type": "string"
          },
          "enabled": {
            "description": "Workflows are created disabled. A workflow can only be enabled once every step is configured.",
            "type": "boolean"
          },
          "event_name": {
            "description": "The custom event that starts this workflow, e.g. `user.signup`. Required for `EVENT` workflows (the default) and ignored for the other trigger types.",
            "maxLength": 200,
            "minLength": 1,
            "type": "string"
          },
          "interval_ms": {
            "description": "For `SCHEDULE` workflows: how often the workflow fires, in milliseconds. Between one minute and 30 days; defaults to one hour.",
            "maximum": 2592000000,
            "minimum": 60000,
            "type": "integer"
          },
          "name": {
            "maxLength": 200,
            "minLength": 1,
            "type": "string"
          },
          "sequence": {
            "description": "Optional LINEAR chain to create the workflow with, in run order. The trigger step is prepended and each step is wired to the next, so this cannot express branches — use `PUT /api/v1/workflows/{id}/graph` for those. Omit it to create a workflow that holds only its trigger step.",
            "items": {
              "$ref": "#/components/schemas/WorkflowSequenceStepV1"
            },
            "maxItems": 199,
            "minItems": 1,
            "type": "array"
          },
          "trigger_type": {
            "$ref": "#/components/schemas/WorkflowTriggerTypeV1"
          }
        },
        "required": [
          "name"
        ],
        "type": "object"
      },
      "WorkflowDelayStepV1": {
        "description": "Pauses the run for `amount` × `unit`, up to 365 days.",
        "properties": {
          "config": {
            "additionalProperties": {
              "additionalProperties": {},
              "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
              "type": [
                "string",
                "number",
                "boolean",
                "object",
                "array",
                "null"
              ]
            },
            "properties": {
              "amount": {
                "exclusiveMinimum": 0,
                "type": "number"
              },
              "unit": {
                "enum": [
                  "minutes",
                  "hours",
                  "days"
                ],
                "type": "string"
              }
            },
            "type": "object"
          },
          "id": {
            "description": "Stable step id. On a graph write it is CHOSEN BY THE CALLER: send back the id you read to keep a step (and its run history), a fresh uuid to add one, and omit an id to delete that step.",
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "maxLength": 100,
            "minLength": 1,
            "type": "string"
          },
          "position": {
            "$ref": "#/components/schemas/WorkflowStepPositionV1"
          },
          "template_id": {
            "description": "The `SEND_EMAIL`/`SEND_AT_OPTIMAL_TIME` template, as a relation. Null for every other step type.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "enum": [
              "DELAY"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "position",
          "type",
          "config"
        ],
        "type": "object"
      },
      "WorkflowDeletedV1": {
        "description": "Confirmation that a workflow was deleted.",
        "properties": {
          "deleted": {
            "enum": [
              true
            ],
            "type": "boolean"
          },
          "id": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "id",
          "deleted"
        ],
        "type": "object"
      },
      "WorkflowExecutionStartV1": {
        "description": "Body for POST /api/v1/workflows/{id}/executions.",
        "properties": {
          "contact_id": {
            "description": "Contact to enter the workflow. Must belong to this project.",
            "format": "uuid",
            "type": "string"
          },
          "context": {
            "additionalProperties": {
              "additionalProperties": {},
              "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
              "type": [
                "string",
                "number",
                "boolean",
                "object",
                "array",
                "null"
              ]
            },
            "description": "Extra variables merged into the contact's data for this run.",
            "type": "object"
          }
        },
        "required": [
          "contact_id"
        ],
        "type": "object"
      },
      "WorkflowExecutionV1": {
        "description": "One contact's run through a workflow.",
        "properties": {
          "completed_at": {
            "format": "date-time",
            "type": [
              "string",
              "null"
            ]
          },
          "contact_id": {
            "format": "uuid",
            "type": "string"
          },
          "current_step_id": {
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "exit_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "started_at": {
            "format": "date-time",
            "type": "string"
          },
          "status": {
            "enum": [
              "RUNNING",
              "WAITING",
              "COMPLETED",
              "EXITED",
              "FAILED",
              "CANCELLED"
            ],
            "type": "string"
          },
          "workflow_id": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "id",
          "workflow_id",
          "contact_id",
          "status",
          "current_step_id",
          "exit_reason",
          "started_at",
          "completed_at"
        ],
        "type": "object"
      },
      "WorkflowExecutionV1List": {
        "description": "Cursor-paginated list of workflow executions, newest first.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/WorkflowExecutionV1"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "description": "Pass as `after` to fetch the next page. `null` on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "type": "object"
      },
      "WorkflowExitStepV1": {
        "description": "Ends the run early and stamps `exit_reason`.",
        "properties": {
          "config": {
            "additionalProperties": {
              "additionalProperties": {},
              "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
              "type": [
                "string",
                "number",
                "boolean",
                "object",
                "array",
                "null"
              ]
            },
            "properties": {
              "reason": {
                "maxLength": 200,
                "type": "string"
              }
            },
            "type": "object"
          },
          "id": {
            "description": "Stable step id. On a graph write it is CHOSEN BY THE CALLER: send back the id you read to keep a step (and its run history), a fresh uuid to add one, and omit an id to delete that step.",
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "maxLength": 100,
            "minLength": 1,
            "type": "string"
          },
          "position": {
            "$ref": "#/components/schemas/WorkflowStepPositionV1"
          },
          "template_id": {
            "description": "The `SEND_EMAIL`/`SEND_AT_OPTIMAL_TIME` template, as a relation. Null for every other step type.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "enum": [
              "EXIT"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "position",
          "type",
          "config"
        ],
        "type": "object"
      },
      "WorkflowGraphReplaceV1": {
        "description": "Body for `PUT /api/v1/workflows/{id}/graph`. Replaces the whole graph: a step whose id you send is kept and updated, a fresh id is created, and a step you omit is deleted along with its run history. Refused with 409 while the workflow has running executions, because those runs are standing on the steps being replaced.",
        "properties": {
          "steps": {
            "description": "The complete step set. Exactly one must be a `TRIGGER`.",
            "items": {
              "$ref": "#/components/schemas/WorkflowStepV1"
            },
            "maxItems": 200,
            "minItems": 1,
            "type": "array"
          },
          "transitions": {
            "description": "The complete edge set. Every `from_step_id`/`to_step_id` must name a step in this same document, and a step may not point at itself.",
            "items": {
              "$ref": "#/components/schemas/WorkflowTransitionV1"
            },
            "maxItems": 400,
            "type": "array"
          }
        },
        "required": [
          "steps",
          "transitions"
        ],
        "type": "object"
      },
      "WorkflowGraphV1": {
        "description": "A workflow's complete step graph. The response of `GET /api/v1/workflows/{id}/graph` is accepted verbatim by `PUT` on the same path.",
        "properties": {
          "steps": {
            "items": {
              "$ref": "#/components/schemas/WorkflowStepReadV1"
            },
            "type": "array"
          },
          "transitions": {
            "items": {
              "$ref": "#/components/schemas/WorkflowTransitionV1"
            },
            "type": "array"
          },
          "version": {
            "description": "The workflow's version AFTER this read. Every structural change bumps it and writes a `workflow_versions` snapshot, so a changed number between two reads means somebody edited the graph.",
            "type": "integer"
          },
          "workflow_id": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "workflow_id",
          "version",
          "steps",
          "transitions"
        ],
        "type": "object"
      },
      "WorkflowSendAtOptimalTimeStepV1": {
        "description": "Like `SEND_EMAIL`, but held until this contact's historically best open hour, falling back to `fallbackHour` and never waiting longer than `maxDelayHours`.",
        "properties": {
          "config": {
            "additionalProperties": {
              "additionalProperties": {},
              "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
              "type": [
                "string",
                "number",
                "boolean",
                "object",
                "array",
                "null"
              ]
            },
            "properties": {
              "fallbackHour": {
                "maximum": 23,
                "minimum": 0,
                "type": "integer"
              },
              "maxDelayHours": {
                "exclusiveMinimum": 0,
                "maximum": 168,
                "type": "number"
              },
              "templateId": {
                "format": "uuid",
                "type": "string"
              }
            },
            "type": "object"
          },
          "id": {
            "description": "Stable step id. On a graph write it is CHOSEN BY THE CALLER: send back the id you read to keep a step (and its run history), a fresh uuid to add one, and omit an id to delete that step.",
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "maxLength": 100,
            "minLength": 1,
            "type": "string"
          },
          "position": {
            "$ref": "#/components/schemas/WorkflowStepPositionV1"
          },
          "template_id": {
            "description": "The `SEND_EMAIL`/`SEND_AT_OPTIMAL_TIME` template, as a relation. Null for every other step type.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "enum": [
              "SEND_AT_OPTIMAL_TIME"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "position",
          "type",
          "config"
        ],
        "type": "object"
      },
      "WorkflowSendEmailStepV1": {
        "description": "Sends one email to the contact. Give it either `template_id` (preferred) or an inline `subject` + `body`.",
        "properties": {
          "config": {
            "additionalProperties": {
              "additionalProperties": {},
              "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
              "type": [
                "string",
                "number",
                "boolean",
                "object",
                "array",
                "null"
              ]
            },
            "properties": {
              "body": {
                "type": "string"
              },
              "recipient": {
                "additionalProperties": {
                  "additionalProperties": {},
                  "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
                  "type": [
                    "string",
                    "number",
                    "boolean",
                    "object",
                    "array",
                    "null"
                  ]
                },
                "properties": {
                  "customEmail": {
                    "format": "email",
                    "type": "string"
                  },
                  "type": {
                    "enum": [
                      "CONTACT",
                      "CUSTOM"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "type"
                ],
                "type": "object"
              },
              "subject": {
                "maxLength": 1000,
                "type": "string"
              },
              "templateId": {
                "format": "uuid",
                "type": "string"
              }
            },
            "type": "object"
          },
          "id": {
            "description": "Stable step id. On a graph write it is CHOSEN BY THE CALLER: send back the id you read to keep a step (and its run history), a fresh uuid to add one, and omit an id to delete that step.",
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "maxLength": 100,
            "minLength": 1,
            "type": "string"
          },
          "position": {
            "$ref": "#/components/schemas/WorkflowStepPositionV1"
          },
          "template_id": {
            "description": "The `SEND_EMAIL`/`SEND_AT_OPTIMAL_TIME` template, as a relation. Null for every other step type.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "enum": [
              "SEND_EMAIL"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "position",
          "type",
          "config"
        ],
        "type": "object"
      },
      "WorkflowSequenceStepTypeV1": {
        "description": "A step kind that may appear in a linear `sequence`. `TRIGGER` is prepended by the server.",
        "enum": [
          "SEND_EMAIL",
          "DELAY",
          "WAIT_FOR_EVENT",
          "CONDITION",
          "EXIT",
          "WEBHOOK",
          "UPDATE_CONTACT",
          "SEND_AT_OPTIMAL_TIME"
        ],
        "type": "string"
      },
      "WorkflowSequenceStepV1": {
        "description": "One step of a linear workflow sequence.",
        "properties": {
          "config": {
            "additionalProperties": {
              "additionalProperties": {},
              "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
              "type": [
                "string",
                "number",
                "boolean",
                "object",
                "array",
                "null"
              ]
            },
            "description": "Step configuration. Keys are camelCase — see `WorkflowStepV1` for the shape each step type expects.",
            "type": "object"
          },
          "name": {
            "description": "Human-readable label, e.g. `Day 0: welcome`.",
            "maxLength": 100,
            "minLength": 1,
            "type": "string"
          },
          "template_id": {
            "description": "For `SEND_EMAIL`: a template in this project.",
            "format": "uuid",
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/WorkflowSequenceStepTypeV1"
          }
        },
        "required": [
          "type",
          "name",
          "config"
        ],
        "type": "object"
      },
      "WorkflowStateChangeV1": {
        "description": "The workflow after a pause or resume, with the number of runs the call stopped.",
        "properties": {
          "cancelled_executions": {
            "description": "Runs stopped by this call. Always 0 for `resume`; on `pause` it is the number of `RUNNING`/`WAITING` executions that were cancelled, which is what makes pausing different from `PATCH { enabled: false }` (that only stops NEW runs starting).",
            "type": "integer"
          },
          "workflow": {
            "$ref": "#/components/schemas/WorkflowV1"
          }
        },
        "required": [
          "workflow",
          "cancelled_executions"
        ],
        "type": "object"
      },
      "WorkflowStatsV1": {
        "description": "Execution, email and conversion totals for one workflow.",
        "properties": {
          "avg_duration_ms": {
            "type": [
              "number",
              "null"
            ]
          },
          "by_status": {
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Execution counts keyed by status; a status with no executions is absent.",
            "type": "object"
          },
          "completion_rate": {
            "description": "Completed ÷ finished executions (0–1). Null until at least one execution has finished.",
            "type": [
              "number",
              "null"
            ]
          },
          "conversions": {
            "items": {
              "properties": {
                "count": {
                  "type": "integer"
                },
                "event_name": {
                  "type": "string"
                },
                "goal_id": {
                  "format": "uuid",
                  "type": "string"
                },
                "name": {
                  "type": "string"
                }
              },
              "required": [
                "goal_id",
                "name",
                "event_name",
                "count"
              ],
              "type": "object"
            },
            "type": "array"
          },
          "emails": {
            "properties": {
              "clicked": {
                "type": "integer"
              },
              "opened": {
                "type": "integer"
              },
              "sent": {
                "type": "integer"
              }
            },
            "required": [
              "sent",
              "opened",
              "clicked"
            ],
            "type": "object"
          },
          "enabled": {
            "type": "boolean"
          },
          "name": {
            "type": "string"
          },
          "step_count": {
            "description": "Steps in the workflow's graph, trigger step included.",
            "type": "integer"
          },
          "total": {
            "type": "integer"
          },
          "trigger_type": {
            "allOf": [
              {
                "$ref": "#/components/schemas/WorkflowTriggerTypeV1"
              },
              {
                "description": "What starts a workflow. `EVENT`: a custom event you record. `MANUAL`: only an explicit API call. `SCHEDULE`: a fixed repeating interval."
              }
            ]
          },
          "workflow_id": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "workflow_id",
          "name",
          "enabled",
          "trigger_type",
          "step_count",
          "total",
          "by_status",
          "completion_rate",
          "avg_duration_ms",
          "emails",
          "conversions"
        ],
        "type": "object"
      },
      "WorkflowStepPositionV1": {
        "additionalProperties": {
          "additionalProperties": {},
          "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
          "type": [
            "string",
            "number",
            "boolean",
            "object",
            "array",
            "null"
          ]
        },
        "description": "Where this step sits on the editor canvas.",
        "properties": {
          "x": {
            "type": "number"
          },
          "y": {
            "type": "number"
          }
        },
        "required": [
          "x",
          "y"
        ],
        "type": "object"
      },
      "WorkflowStepReadV1": {
        "description": "One node of a workflow graph, as read.",
        "properties": {
          "config": {
            "additionalProperties": {
              "additionalProperties": {},
              "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
              "type": [
                "string",
                "number",
                "boolean",
                "object",
                "array",
                "null"
              ]
            },
            "description": "The step's configuration, exactly as stored. See `WorkflowStepV1` for the keys each step type uses; keys are camelCase.",
            "type": "object"
          },
          "id": {
            "description": "Stable step id. On a graph write it is CHOSEN BY THE CALLER: send back the id you read to keep a step (and its run history), a fresh uuid to add one, and omit an id to delete that step.",
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "maxLength": 100,
            "minLength": 1,
            "type": "string"
          },
          "position": {
            "$ref": "#/components/schemas/WorkflowStepPositionV1"
          },
          "template_id": {
            "description": "The `SEND_EMAIL`/`SEND_AT_OPTIMAL_TIME` template, as a relation. Null for every other step type.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "enum": [
              "TRIGGER",
              "SEND_EMAIL",
              "DELAY",
              "WAIT_FOR_EVENT",
              "CONDITION",
              "EXIT",
              "WEBHOOK",
              "UPDATE_CONTACT",
              "SEND_AT_OPTIMAL_TIME"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "position",
          "type",
          "config"
        ],
        "type": "object"
      },
      "WorkflowStepV1": {
        "description": "One node of a workflow graph.",
        "discriminator": {
          "mapping": {
            "CONDITION": "#/components/schemas/WorkflowConditionStepV1",
            "DELAY": "#/components/schemas/WorkflowDelayStepV1",
            "EXIT": "#/components/schemas/WorkflowExitStepV1",
            "SEND_AT_OPTIMAL_TIME": "#/components/schemas/WorkflowSendAtOptimalTimeStepV1",
            "SEND_EMAIL": "#/components/schemas/WorkflowSendEmailStepV1",
            "TRIGGER": "#/components/schemas/WorkflowTriggerStepV1",
            "UPDATE_CONTACT": "#/components/schemas/WorkflowUpdateContactStepV1",
            "WAIT_FOR_EVENT": "#/components/schemas/WorkflowWaitForEventStepV1",
            "WEBHOOK": "#/components/schemas/WorkflowWebhookStepV1"
          },
          "propertyName": "type"
        },
        "oneOf": [
          {
            "$ref": "#/components/schemas/WorkflowTriggerStepV1"
          },
          {
            "$ref": "#/components/schemas/WorkflowSendEmailStepV1"
          },
          {
            "$ref": "#/components/schemas/WorkflowDelayStepV1"
          },
          {
            "$ref": "#/components/schemas/WorkflowWaitForEventStepV1"
          },
          {
            "$ref": "#/components/schemas/WorkflowConditionStepV1"
          },
          {
            "$ref": "#/components/schemas/WorkflowExitStepV1"
          },
          {
            "$ref": "#/components/schemas/WorkflowWebhookStepV1"
          },
          {
            "$ref": "#/components/schemas/WorkflowUpdateContactStepV1"
          },
          {
            "$ref": "#/components/schemas/WorkflowSendAtOptimalTimeStepV1"
          }
        ]
      },
      "WorkflowTransitionV1": {
        "description": "One directed edge between two steps.",
        "properties": {
          "condition": {
            "additionalProperties": {},
            "description": "Null to always follow this edge. From a `CONDITION` step, `{ \"branch\": \"yes\" }`, `{ \"branch\": \"no\" }`, or `{ \"branch\": \"<branch id>\" }` in the multi form.",
            "type": [
              "string",
              "number",
              "boolean",
              "object",
              "array",
              "null"
            ]
          },
          "from_step_id": {
            "format": "uuid",
            "type": "string"
          },
          "id": {
            "description": "Caller-chosen on a write, exactly like a step id.",
            "format": "uuid",
            "type": "string"
          },
          "priority": {
            "description": "Evaluation order among the edges leaving one step; lowest first.",
            "minimum": 0,
            "type": "integer"
          },
          "to_step_id": {
            "format": "uuid",
            "type": "string"
          }
        },
        "required": [
          "id",
          "from_step_id",
          "to_step_id",
          "condition",
          "priority"
        ],
        "type": "object"
      },
      "WorkflowTriggerStepV1": {
        "description": "The graph's single entry node. Its config mirrors the workflow's own trigger: `eventName` for `EVENT`, `intervalMs` for `SCHEDULE`, empty for `MANUAL`.",
        "properties": {
          "config": {
            "additionalProperties": {
              "additionalProperties": {},
              "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
              "type": [
                "string",
                "number",
                "boolean",
                "object",
                "array",
                "null"
              ]
            },
            "properties": {
              "eventName": {
                "maxLength": 200,
                "minLength": 1,
                "type": "string"
              },
              "intervalMs": {
                "exclusiveMinimum": 0,
                "type": "integer"
              }
            },
            "type": "object"
          },
          "id": {
            "description": "Stable step id. On a graph write it is CHOSEN BY THE CALLER: send back the id you read to keep a step (and its run history), a fresh uuid to add one, and omit an id to delete that step.",
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "maxLength": 100,
            "minLength": 1,
            "type": "string"
          },
          "position": {
            "$ref": "#/components/schemas/WorkflowStepPositionV1"
          },
          "template_id": {
            "description": "The `SEND_EMAIL`/`SEND_AT_OPTIMAL_TIME` template, as a relation. Null for every other step type.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "enum": [
              "TRIGGER"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "position",
          "type",
          "config"
        ],
        "type": "object"
      },
      "WorkflowTriggerTypeV1": {
        "description": "What starts this workflow. Defaults to `EVENT`, which is the only kind the API created before this field existed. `EVENT` requires `event_name`; `SCHEDULE` uses `interval_ms`; `MANUAL` is started only by `POST /api/v1/workflows/{id}/executions`.",
        "enum": [
          "EVENT",
          "MANUAL",
          "SCHEDULE"
        ],
        "type": "string"
      },
      "WorkflowUpdateContactStepV1": {
        "description": "Writes `updates` onto the contact, and optionally flips `subscribed`.",
        "properties": {
          "config": {
            "additionalProperties": {
              "additionalProperties": {},
              "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
              "type": [
                "string",
                "number",
                "boolean",
                "object",
                "array",
                "null"
              ]
            },
            "properties": {
              "subscribed": {
                "type": "boolean"
              },
              "updates": {
                "additionalProperties": {
                  "additionalProperties": {},
                  "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
                  "type": [
                    "string",
                    "number",
                    "boolean",
                    "object",
                    "array",
                    "null"
                  ]
                },
                "type": "object"
              }
            },
            "type": "object"
          },
          "id": {
            "description": "Stable step id. On a graph write it is CHOSEN BY THE CALLER: send back the id you read to keep a step (and its run history), a fresh uuid to add one, and omit an id to delete that step.",
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "maxLength": 100,
            "minLength": 1,
            "type": "string"
          },
          "position": {
            "$ref": "#/components/schemas/WorkflowStepPositionV1"
          },
          "template_id": {
            "description": "The `SEND_EMAIL`/`SEND_AT_OPTIMAL_TIME` template, as a relation. Null for every other step type.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "enum": [
              "UPDATE_CONTACT"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "position",
          "type",
          "config"
        ],
        "type": "object"
      },
      "WorkflowUpdateV1": {
        "description": "Body for PATCH /api/v1/workflows/{id}. Every field is optional; omitted fields are left unchanged. Changing the trigger while executions are running answers 409.",
        "properties": {
          "allow_reentry": {
            "type": "boolean"
          },
          "description": {
            "maxLength": 1000,
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "event_name": {
            "maxLength": 200,
            "minLength": 1,
            "type": "string"
          },
          "interval_ms": {
            "description": "For `SCHEDULE` workflows: how often the workflow fires, in milliseconds. Between one minute and 30 days; defaults to one hour.",
            "maximum": 2592000000,
            "minimum": 60000,
            "type": "integer"
          },
          "max_executions_per_hour": {
            "description": "Per-workflow start rate cap. `null` removes the cap.",
            "exclusiveMinimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "name": {
            "maxLength": 200,
            "minLength": 1,
            "type": "string"
          },
          "sequence": {
            "description": "Optional LINEAR chain that REPLACES every non-trigger step, in run order. Destructive: the existing steps are discarded and their ids do not survive, which is why omitting this field leaves the graph untouched. Branches need `PUT /api/v1/workflows/{id}/graph` instead.",
            "items": {
              "$ref": "#/components/schemas/WorkflowSequenceStepV1"
            },
            "maxItems": 199,
            "minItems": 1,
            "type": "array"
          },
          "trigger_type": {
            "allOf": [
              {
                "$ref": "#/components/schemas/WorkflowTriggerTypeV1"
              },
              {
                "description": "What starts a workflow. `EVENT`: a custom event you record. `MANUAL`: only an explicit API call. `SCHEDULE`: a fixed repeating interval."
              }
            ]
          }
        },
        "type": "object"
      },
      "WorkflowV1": {
        "description": "An automation workflow as exposed on the v1 API.",
        "properties": {
          "allow_reentry": {
            "type": "boolean"
          },
          "created_at": {
            "format": "date-time",
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "enabled": {
            "type": "boolean"
          },
          "event_name": {
            "description": "Trigger event for `EVENT` workflows; null for the other trigger types.",
            "type": [
              "string",
              "null"
            ]
          },
          "id": {
            "format": "uuid",
            "type": "string"
          },
          "max_executions_per_hour": {
            "type": [
              "integer",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "step_count": {
            "description": "Steps in this workflow's graph, trigger step included — so a workflow that has only ever been created reports 1. Read `/graph` for the steps themselves.",
            "type": "integer"
          },
          "trigger_type": {
            "enum": [
              "EVENT",
              "MANUAL",
              "SCHEDULE"
            ],
            "type": "string"
          },
          "updated_at": {
            "format": "date-time",
            "type": "string"
          },
          "version": {
            "description": "Incremented on every structural (step/transition) change.",
            "type": "integer"
          }
        },
        "required": [
          "id",
          "name",
          "description",
          "enabled",
          "trigger_type",
          "event_name",
          "allow_reentry",
          "max_executions_per_hour",
          "version",
          "step_count",
          "created_at",
          "updated_at"
        ],
        "type": "object"
      },
      "WorkflowV1List": {
        "description": "Cursor-paginated list of workflows.",
        "properties": {
          "data": {
            "items": {
              "$ref": "#/components/schemas/WorkflowV1"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "next_cursor": {
            "description": "Pass as `after` to fetch the next page. `null` on the last page.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "data",
          "has_more",
          "next_cursor"
        ],
        "type": "object"
      },
      "WorkflowWaitForEventStepV1": {
        "description": "Parks the run until `eventName` is recorded for this contact, or `timeout` seconds pass.",
        "properties": {
          "config": {
            "additionalProperties": {
              "additionalProperties": {},
              "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
              "type": [
                "string",
                "number",
                "boolean",
                "object",
                "array",
                "null"
              ]
            },
            "properties": {
              "eventName": {
                "maxLength": 200,
                "minLength": 1,
                "type": "string"
              },
              "timeout": {
                "exclusiveMinimum": 0,
                "maximum": 31536000,
                "type": "number"
              }
            },
            "type": "object"
          },
          "id": {
            "description": "Stable step id. On a graph write it is CHOSEN BY THE CALLER: send back the id you read to keep a step (and its run history), a fresh uuid to add one, and omit an id to delete that step.",
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "maxLength": 100,
            "minLength": 1,
            "type": "string"
          },
          "position": {
            "$ref": "#/components/schemas/WorkflowStepPositionV1"
          },
          "template_id": {
            "description": "The `SEND_EMAIL`/`SEND_AT_OPTIMAL_TIME` template, as a relation. Null for every other step type.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "enum": [
              "WAIT_FOR_EVENT"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "position",
          "type",
          "config"
        ],
        "type": "object"
      },
      "WorkflowWebhookStepV1": {
        "description": "Calls an external URL. `url`, header values and the JSON body's string leaves are `{{variable}}`-interpolated from the contact and the run's variables.",
        "properties": {
          "config": {
            "additionalProperties": {
              "additionalProperties": {},
              "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
              "type": [
                "string",
                "number",
                "boolean",
                "object",
                "array",
                "null"
              ]
            },
            "properties": {
              "body": {
                "additionalProperties": {},
                "description": "Arbitrary JSON value (string, number, boolean, null, array, or object).",
                "type": [
                  "string",
                  "number",
                  "boolean",
                  "object",
                  "array",
                  "null"
                ]
              },
              "headers": {
                "additionalProperties": {
                  "type": "string"
                },
                "type": "object"
              },
              "method": {
                "enum": [
                  "GET",
                  "POST",
                  "PUT",
                  "PATCH",
                  "DELETE"
                ],
                "type": "string"
              },
              "url": {
                "format": "uri",
                "type": "string"
              }
            },
            "type": "object"
          },
          "id": {
            "description": "Stable step id. On a graph write it is CHOSEN BY THE CALLER: send back the id you read to keep a step (and its run history), a fresh uuid to add one, and omit an id to delete that step.",
            "format": "uuid",
            "type": "string"
          },
          "name": {
            "maxLength": 100,
            "minLength": 1,
            "type": "string"
          },
          "position": {
            "$ref": "#/components/schemas/WorkflowStepPositionV1"
          },
          "template_id": {
            "description": "The `SEND_EMAIL`/`SEND_AT_OPTIMAL_TIME` template, as a relation. Null for every other step type.",
            "format": "uuid",
            "type": [
              "string",
              "null"
            ]
          },
          "type": {
            "enum": [
              "WEBHOOK"
            ],
            "type": "string"
          }
        },
        "required": [
          "id",
          "name",
          "position",
          "type",
          "config"
        ],
        "type": "object"
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "bearerFormat": "API Key",
        "description": "API key authentication. Use a `sk_*` (FULL) or `pk_*` (SENDING_ONLY) key as the bearer token. Public keys (`pk_*`) are restricted to the email-send endpoints and the self-serve list subscribe/unsubscribe pair; every other endpoint — event tracking included — answers 403 for them. In scope terms (used by `/api/v1/*` operations): FULL keys hold every scope; SENDING_ONLY keys hold only `emails:send`.",
        "scheme": "bearer",
        "type": "http"
      },
      "OAuth2": {
        "description": "OAuth 2.1 with PKCE, for AI agents and other delegated clients (this is what the MCP endpoint at `/api/mcp` uses). Tokens are minted through the consent screen and carry ONLY the scopes the user ticked there, so an operation lists the single scope it requires and a token without it answers `403` with code `SCOPE_MISSING` — before any input is parsed. Unlike an API key, a delegated token reaches an operation only where the route itself declares a scope; every other route refuses it outright.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://app.sendly.now/api/auth/oauth2/authorize",
            "scopes": {
              "analytics:read": "View your sending analytics and engagement metrics",
              "api-keys:read": "See which API keys exist, including what each one is allowed to do",
              "api-keys:write": "Create, rotate, and revoke API keys — these keep working even after you disconnect this app",
              "campaigns:read": "View your campaigns and their performance",
              "campaigns:send": "Send or schedule your campaigns to their audience",
              "campaigns:write": "Create, edit, and organize your campaigns",
              "contacts:read": "View your contacts and their custom fields",
              "contacts:write": "Create, update, and delete your contacts",
              "deliverability:read": "Check why mail from one of your domains is not arriving",
              "domains:read": "View your sending domains and their verification status",
              "domains:write": "Add and remove sending domains, and trigger verification",
              "emails:read": "View the emails you have sent and their delivery status",
              "emails:send": "Send emails from your verified domains",
              "emails:test": "Send test emails to your own address from the Sendly sandbox",
              "events:read": "View the custom events your application has recorded",
              "events:write": "Record custom events for your contacts",
              "lists:read": "View your subscriber lists and who is on them",
              "lists:write": "Create, rename, and delete your subscriber lists",
              "mailboxes:read": "View the mailboxes on your domains and their settings",
              "mailboxes:send": "Write and send new email from your hosted mailboxes, as that address",
              "mailboxes:write": "Create and delete mailboxes on your verified domains",
              "projects:read": "View your projects and their settings",
              "projects:write": "Create new projects on your account",
              "segments:read": "View your segments and who belongs to them",
              "segments:write": "Create, edit, and delete your segments",
              "suppression:read": "View the addresses on your suppression list",
              "suppression:write": "Add and remove addresses on your suppression list",
              "templates:read": "View your email templates",
              "templates:write": "Create, edit, and delete your email templates",
              "topics:read": "View the topics you mail about and who is subscribed to each",
              "topics:write": "Create and edit topics, and change what your contacts are subscribed to — this decides who your campaigns reach",
              "usage:read": "View your usage totals and billing limits",
              "validation:read": "View your email validation runs and their results",
              "validation:write": "Check whether email addresses can receive mail — this is billed per address",
              "webhooks:read": "View your webhook endpoints and their delivery history",
              "webhooks:write": "Create, edit, and delete your webhook endpoints",
              "workflows:read": "View your automation workflows and their runs",
              "workflows:write": "Create, edit, enable, and delete your automation workflows"
            },
            "tokenUrl": "https://app.sendly.now/api/auth/oauth2/token"
          }
        },
        "type": "oauth2"
      },
      "SessionAuth": {
        "description": "BetterAuth session cookie. Used by the dashboard / browser clients. When present, the active project is taken from the `x-project-id` header.",
        "in": "cookie",
        "name": "better-auth.session_token",
        "type": "apiKey"
      }
    }
  },
  "info": {
    "contact": {
      "name": "Sendly Support",
      "url": "https://sendly.now"
    },
    "description": "Sendly's public REST API. Authenticate with a project API key as `Authorization: Bearer <api-key>` (`sk_*` for full access, `pk_*` for sending-only), with a BetterAuth session cookie, or — for AI agents and other delegated clients — with an OAuth 2.1 access token carrying the scopes its user approved. An operation lists the scope it requires under `OAuth2`; an operation that lists none refuses delegated tokens outright, whatever scopes they hold. Legacy `/api/*` endpoints return JSON envelopes of the form `{ success, data }` (success) or `{ error: { message, code } }` (failure). `/api/v1/*` endpoints return bare resource bodies on success and RFC 9457 `application/problem+json` documents on failure.",
    "license": {
      "name": "AGPL-3.0",
      "url": "https://www.gnu.org/licenses/agpl-3.0.txt"
    },
    "title": "Sendly API",
    "version": "1.0.0"
  },
  "openapi": "3.1.0",
  "paths": {
    "/api/contacts": {
      "get": {
        "description": "Cursor-paginated list of contacts. Supports filter by `search` and `subscribed`.\n\nRequires the `contacts:read` scope — View your contacts and their custom fields.",
        "operationId": "listContacts",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "search",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "subscribed",
            "required": false,
            "schema": {
              "enum": [
                "true",
                "false"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactListResponse"
                }
              }
            },
            "description": "Contact list"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation failed — request body or query parameters did not match the schema"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "contacts:read"
            ]
          }
        ],
        "summary": "List contacts",
        "tags": [
          "Contacts"
        ]
      },
      "post": {
        "description": "Create a new contact. Returns 409 on `(projectId, email)` conflict — use `/api/contacts/upsert` for create-or-update semantics.\n\nRequires the `contacts:write` scope — Create, update, and delete your contacts.",
        "operationId": "createContact",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateContact"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Contact"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Contact created"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Email already exists for this project"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation failed — request body or query parameters did not match the schema"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "contacts:write"
            ]
          }
        ],
        "summary": "Create a contact",
        "tags": [
          "Contacts"
        ]
      }
    },
    "/api/contacts/bulk": {
      "delete": {
        "description": "Delete up to 1000 contacts in one call. Provide either `ids` or `emails`.\n\nRequires the `contacts:write` scope — Create, update, and delete your contacts.",
        "operationId": "bulkDeleteContacts",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactBulkDeleteBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "deleted": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "deleted"
                      ],
                      "type": "object"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Bulk-delete result"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation failed — request body or query parameters did not match the schema"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "contacts:write"
            ]
          }
        ],
        "summary": "Bulk-delete contacts",
        "tags": [
          "Contacts"
        ]
      },
      "post": {
        "description": "Create up to 1000 contacts in one call. Per-row conflicts are reported as `skipped`.\n\nRequires the `contacts:write` scope — Create, update, and delete your contacts.",
        "operationId": "bulkCreateContacts",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactBulkCreateBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "created": {
                          "type": "integer"
                        },
                        "errors": {
                          "items": {
                            "properties": {
                              "index": {
                                "type": "integer"
                              },
                              "message": {
                                "type": "string"
                              }
                            },
                            "required": [
                              "index",
                              "message"
                            ],
                            "type": "object"
                          },
                          "type": "array"
                        },
                        "skipped": {
                          "type": "integer"
                        }
                      },
                      "required": [
                        "created",
                        "skipped",
                        "errors"
                      ],
                      "type": "object"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Bulk-create result"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation failed — request body or query parameters did not match the schema"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "contacts:write"
            ]
          }
        ],
        "summary": "Bulk-create contacts",
        "tags": [
          "Contacts"
        ]
      }
    },
    "/api/contacts/upsert": {
      "post": {
        "description": "Idempotent contact upsert keyed by email. Always answers 200 — the create-vs-update distinction is not signalled via status code.\n\nRequires the `contacts:write` scope — Create, update, and delete your contacts.",
        "operationId": "upsertContact",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateContact"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Contact"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Contact created or updated"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation failed — request body or query parameters did not match the schema"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "contacts:write"
            ]
          }
        ],
        "summary": "Create or update a contact by email",
        "tags": [
          "Contacts"
        ]
      }
    },
    "/api/contacts/{id}": {
      "delete": {
        "description": "Hard-delete a contact. Answers 200 with `{ success, data: { id } }` (pre-seam this was 204 No Content).\n\nRequires the `contacts:write` scope — Create, update, and delete your contacts.",
        "operationId": "deleteContact",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdResponse"
                }
              }
            },
            "description": "Contact deleted"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "contacts:write"
            ]
          }
        ],
        "summary": "Delete a contact",
        "tags": [
          "Contacts"
        ]
      },
      "get": {
        "description": "Requires the `contacts:read` scope — View your contacts and their custom fields.",
        "operationId": "getContact",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Contact"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Contact"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "contacts:read"
            ]
          }
        ],
        "summary": "Get a contact",
        "tags": [
          "Contacts"
        ]
      },
      "patch": {
        "description": "Update `data` and/or `subscribed`. `email` is immutable here — use `/api/contacts/upsert` to change addresses.\n\nRequires the `contacts:write` scope — Create, update, and delete your contacts.",
        "operationId": "updateContact",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateContactBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Contact"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Updated contact"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation failed — request body or query parameters did not match the schema"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "contacts:write"
            ]
          }
        ],
        "summary": "Update a contact",
        "tags": [
          "Contacts"
        ]
      }
    },
    "/api/domains": {
      "get": {
        "description": "List all domains for the authenticated project.\n\nRequires the `domains:read` scope — View your sending domains and their verification status.",
        "operationId": "listDomains",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DomainListResponse"
                }
              }
            },
            "description": "Domain list"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "domains:read"
            ]
          }
        ],
        "summary": "List sending domains",
        "tags": [
          "Domains"
        ]
      },
      "post": {
        "description": "Register a new domain with SES and persist its DKIM tokens.\n\nRequires the `domains:write` scope — Add and remove sending domains, and trigger verification.",
        "operationId": "addDomain",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddDomainBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Domain"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Domain added"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          },
          "502": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "AWS SES rejected the identity setup (unusable credentials, a missing IAM permission, or a refusal on Amazon's side). No domain row is written, and the request is not at fault — retrying it unchanged will not help."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "domains:write"
            ]
          }
        ],
        "summary": "Add a sending domain",
        "tags": [
          "Domains"
        ]
      }
    },
    "/api/domains/{id}": {
      "delete": {
        "description": "Removes the domain from the project. The underlying SES identity is also dropped if no other project still uses it.\n\nRequires the `domains:write` scope — Add and remove sending domains, and trigger verification.",
        "operationId": "deleteDomain",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEmpty"
                }
              }
            },
            "description": "Domain removed"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "domains:write"
            ]
          }
        ],
        "summary": "Remove a sending domain",
        "tags": [
          "Domains"
        ]
      },
      "get": {
        "description": "Requires the `domains:read` scope — View your sending domains and their verification status.",
        "operationId": "getDomain",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Domain"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Domain"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "domains:read"
            ]
          }
        ],
        "summary": "Get a sending domain",
        "tags": [
          "Domains"
        ]
      },
      "patch": {
        "description": "Assign this identity to transactional or marketing traffic, make it the project's default for that stream, or give it the from-address a send on that stream uses when it names none.\n\nStreams are enforced, not labelled: once an identity is assigned, a send of the other kind from it is refused with 403. That is what keeps a campaign's complaint rate off the identity your password resets go out on. An identity with no stream (`stream: null`, and the state of every domain added before this existed) carries both.\n\nAt most one identity per (project, stream) is the default; setting `streamDefault` demotes whichever held it. `defaultFromAddress` has to be an address on this identity's own host — a default pointing anywhere else would go out unsigned by the name in the From header. Every field is optional and an omitted one is left alone.\n\nRequires the `domains:write` scope — Add and remove sending domains, and trigger verification.",
        "operationId": "assignDomainStream",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AssignDomainStream"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Domain"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Updated sending identity"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "domains:write"
            ]
          }
        ],
        "summary": "Assign a sending identity to a stream",
        "tags": [
          "Domains"
        ]
      }
    },
    "/api/domains/{id}/dodomain-session": {
      "post": {
        "description": "Mint a short-lived guided-setup session for this domain and return the URL that opens it. The URL is the whole point: DNS records have to be published at the domain's registrar, which is a place only a person with those credentials can reach — so this call cannot finish the job, it hands it over.\n\nThe session is bound to this one domain, expires on its own, and returns the browser to the Sendly domains settings page when it is done. Verification authority stays with SES either way: guided setup publishes the records, it does not decide whether they are correct.\n\n`503 DODOMAIN_NOT_CONFIGURED` when the deployment has no guided-setup provider. `429 DODOMAIN_SESSION_COOLDOWN` for a second call within 60s on the same domain — each session is metered, so a double submit is refused rather than charged twice.\n\nRequires the `domains:write` scope — Add and remove sending domains, and trigger verification.",
        "operationId": "startDomainSetup",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "connectUrl": {
                          "description": "Open this in a browser to publish the records. Short-lived and domain-specific.",
                          "format": "uri",
                          "type": "string"
                        },
                        "expiresAt": {
                          "description": "When `connectUrl` stops working.",
                          "type": "string"
                        },
                        "token": {
                          "type": "string"
                        }
                      },
                      "required": [
                        "token",
                        "connectUrl",
                        "expiresAt"
                      ],
                      "type": "object"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Guided setup session"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "domains:write"
            ]
          }
        ],
        "summary": "Start guided DNS setup",
        "tags": [
          "Domains"
        ]
      }
    },
    "/api/domains/{id}/verify": {
      "get": {
        "description": "Read the current SES verification status without forcing a refresh.\n\nRequires the `domains:write` scope — Add and remove sending domains, and trigger verification.",
        "operationId": "getDomainVerification",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DomainVerificationStatus"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Verification status"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "domains:write"
            ]
          }
        ],
        "summary": "Read SES verification status",
        "tags": [
          "Domains"
        ]
      },
      "post": {
        "description": "Force a refresh of the domain's SES verification status.\n\nRequires the `domains:write` scope — Add and remove sending domains, and trigger verification.",
        "operationId": "verifyDomain",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/DomainVerificationStatus"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Verification status"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "domains:write"
            ]
          }
        ],
        "summary": "Trigger SES verification",
        "tags": [
          "Domains"
        ]
      }
    },
    "/api/emails": {
      "get": {
        "description": "List emails for the authenticated project. Cursor-paginated for stable scroll over large result sets.\n\nRequires the `emails:read` scope — View the emails you have sent and their delivery status.",
        "operationId": "listEmails",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "tag",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "description": "Delivery lifecycle of the message. Engagement is reported separately.",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "$ref": "#/components/schemas/EmailDeliveryStatus"
            }
          },
          {
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailListResponse"
                }
              }
            },
            "description": "Email list"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "emails:read"
            ]
          }
        ],
        "summary": "List emails",
        "tags": [
          "Emails"
        ]
      },
      "post": {
        "description": "Send a single transactional email. Accepts a `template` ID or an inline `subject` + `body`. An optional `Idempotency-Key` request header (1–255 chars, 24h TTL) ensures replay safety: the first request wins and a retry carrying the same key AND the same body replays its response. Reusing a key with a DIFFERENT body answers `422 IDEMPOTENCY_KEY_REUSED` — a key names one request, so it is never silently served another request's result.\n\nRequires the `emails:send` scope — Send emails from your verified domains.",
        "operationId": "sendEmail",
        "parameters": [
          {
            "description": "Replay-safety key (24h TTL). Reuse it only to retry the identical request.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "description": "Replay-safety key (24h TTL). Reuse it only to retry the identical request.",
              "maxLength": 255,
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendEmail"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SendEmailResponse"
                }
              }
            },
            "description": "Email accepted / sent"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error, or `TOO_MANY_UNIQUE_TEMPLATES` — a new account submitted more distinct message bodies in one request than content review allows."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions, project disabled, or `CONTENT_REJECTED`: the message was flagged by automated content review and was not sent."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`CONFLICT` — a request with this `Idempotency-Key` is still in flight. Retry shortly."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`IDEMPOTENCY_KEY_REUSED` — this `Idempotency-Key` was already used for a request with a different body. Reuse a key only to retry the identical request; otherwise send a new key."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`CONTENT_REVIEW_UNAVAILABLE` — content review could not run for this new account, so the message was not accepted. Safe to retry after a short delay."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "emails:send"
            ]
          }
        ],
        "summary": "Send a single transactional email",
        "tags": [
          "Emails"
        ]
      }
    },
    "/api/emails/batch": {
      "post": {
        "description": "Send up to 100 emails in one request. Returns 207 Multi-Status if any entry failed, or 200 if all succeeded. Per-entry results are reported in the `data` array.\n\nThe whole batch is ONE idempotent unit: an `Idempotency-Key` replayed with the same entry list replays the same per-index results, and replaying it with an edited list answers `422 IDEMPOTENCY_KEY_REUSED` rather than returning results for indexes the new body no longer has.\n\nRequires the `emails:send` scope — Send emails from your verified domains.",
        "operationId": "sendEmailBatch",
        "parameters": [
          {
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "maxLength": 255,
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchSendBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchSendResponse"
                }
              }
            },
            "description": "All entries sent"
          },
          "207": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchSendResponse"
                }
              }
            },
            "description": "Partial success — at least one entry failed"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error, or `TOO_MANY_UNIQUE_TEMPLATES` — a new account submitted more distinct message bodies in one request than content review allows."
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions, project disabled, or `CONTENT_REJECTED`: the message was flagged by automated content review and was not sent."
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`CONFLICT` — a request with this `Idempotency-Key` is still in flight. Retry shortly."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`IDEMPOTENCY_KEY_REUSED` — this `Idempotency-Key` was already used for a request with a different body. Reuse a key only to retry the identical request; otherwise send a new key."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "`CONTENT_REVIEW_UNAVAILABLE` — content review could not run for this new account, so the message was not accepted. Safe to retry after a short delay."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "emails:send"
            ]
          }
        ],
        "summary": "Send a batch of emails",
        "tags": [
          "Emails"
        ]
      }
    },
    "/api/emails/{id}": {
      "get": {
        "description": "Fetch one email together with its DELIVERY history — the transitions behind `status`, oldest first.\n\n`events` here is not the custom-event resource: the events a caller records with `POST /api/v1/events` are read from `GET /api/v1/events`, and never appear on this response.\n\nRequires the `emails:read` scope — View the emails you have sent and their delivery status.",
        "operationId": "getEmail",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailDetailResponse"
                }
              }
            },
            "description": "Email and its delivery history"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "emails:read"
            ]
          }
        ],
        "summary": "Get a single email",
        "tags": [
          "Emails"
        ]
      }
    },
    "/api/emails/{id}/schedule": {
      "delete": {
        "description": "Mark a still-pending email as FAILED before the worker picks it up. Returns 409 if the email has already left PENDING.",
        "operationId": "cancelScheduledEmail",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailResponse"
                }
              }
            },
            "description": "Email cancelled"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Email already past PENDING"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          }
        ],
        "summary": "Cancel a scheduled (still-PENDING) email",
        "tags": [
          "Emails"
        ]
      }
    },
    "/api/lists/{id}/subscribe": {
      "post": {
        "description": "Add a contact to a list, creating the contact if it does not exist. When the list has `doubleOptIn` enabled the membership is created as `PENDING` and the response carries a `confirmToken` — Sendly does NOT send the confirmation email, so the caller must deliver `/api/lists/confirm-subscription?token=<confirmToken>` to the contact itself.\n\nAccepts SENDING_ONLY (`pk_*`) keys so it can back a public subscribe form.\n\n**Re-subscribing after an opt-out.** If the email already holds an `UNSUBSCRIBED` membership on this list, the call fails with `409 RESUBSCRIBE_CONFIRMATION_REQUIRED` unless the body sets `allowResubscribe: true`. Reversing an opt-out is a consent decision, so it is never the default — set the flag only when the contact themselves asked to be re-subscribed.\n\n`previousStatus` reports the membership's status before the call (`null` when it did not exist); prefer it over `created` when describing what changed, since `created: false` is equally true for an unchanged membership and for a reactivated one.",
        "operationId": "subscribeToList",
        "parameters": [
          {
            "description": "List id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "List id.",
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListSubscribe"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListSubscribeResponse"
                }
              }
            },
            "description": "Contact subscribed, or an existing membership returned unchanged"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The contact previously unsubscribed from this list and `allowResubscribe` was not set. `error.code` is `RESUBSCRIBE_CONFIRMATION_REQUIRED` and `error.details.previousStatus` is `UNSUBSCRIBED`. Retry with `allowResubscribe: true` once the contact has consented."
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation failed — request body or query parameters did not match the schema"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          }
        ],
        "summary": "Subscribe a contact to a list",
        "tags": [
          "Lists"
        ]
      }
    },
    "/api/lists/{id}/unsubscribe": {
      "post": {
        "description": "Mark the contact's membership on this list as `UNSUBSCRIBED`. Accepts SENDING_ONLY (`pk_*`) keys so it can back a public preference form. Idempotent — unsubscribing an address that is not a member succeeds.\n\nOnce a membership is `UNSUBSCRIBED`, a later `POST /api/lists/{id}/subscribe` needs `allowResubscribe: true` to reverse it.",
        "operationId": "unsubscribeFromList",
        "parameters": [
          {
            "description": "List id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "List id.",
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListUnsubscribe"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListUnsubscribeResponse"
                }
              }
            },
            "description": "Contact unsubscribed"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation failed — request body or query parameters did not match the schema"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          }
        ],
        "summary": "Unsubscribe a contact from a list",
        "tags": [
          "Lists"
        ]
      }
    },
    "/api/mailboxes": {
      "get": {
        "description": "Every mailbox on the authenticated project's domains, newest first. Not paginated: a project is capped at ten mailboxes, and the cap counts only those holding — or on their way to holding — a real account (`PROVISIONING`, `ACTIVE`, `SUSPENDED`). `FAILED` rows do not consume the cap but ARE returned here, so a project that has had failed provisions can list more than ten.\n\nThis lists the mailboxes themselves, never their contents: the messages a mailbox has received are not part of the public API and are not covered by this scope.\n\nRequires the `mailboxes:read` scope — View the mailboxes on your domains and their settings.",
        "operationId": "listMailboxes",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/Mailbox"
                      },
                      "type": "array"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Mailbox list"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "mailboxes:read"
            ]
          }
        ],
        "summary": "List mailboxes",
        "tags": [
          "Mailboxes"
        ]
      },
      "post": {
        "description": "Provision a real receiving mailbox — `support@yourdomain.com` — on a domain you have already verified.\n\nThree consequences worth knowing before you call it:\n\n- **It changes how your domain's mail is routed.** The first mailbox on a domain turns receiving on for that domain, so mail addressed there starts arriving at Sendly instead of wherever it went before.\n- **The domain must be verified.** An unverified domain answers 409; creating a mailbox is not a way to skip DNS.\n- **Ten per project.** The eleventh answers 409. Deleted mailboxes free their slot; failed ones never consumed one.\n\nRetrying a failed provision with the same address reclaims the failed row rather than answering 409 — but only for the same project and the same domain.\n\n`quotaBytes` is accepted by the schema and REFUSED with a 400. Quotas are not implemented, and the field is still parsed so that asking for one is an error rather than a silently dropped key.\n\nRequires the `mailboxes:write` scope — Create and delete mailboxes on your verified domains.",
        "operationId": "createMailbox",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateMailboxBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Mailbox"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Mailbox provisioned"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The address already exists, the domain is not verified, or the project is at its 10-mailbox limit."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          },
          "502": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Provisioning failed in the mail server. The mailbox row is left `FAILED` and can be retried."
          }
        },
        "security": [
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "mailboxes:write"
            ]
          }
        ],
        "summary": "Create a mailbox",
        "tags": [
          "Mailboxes"
        ]
      }
    },
    "/api/mailboxes/{id}": {
      "delete": {
        "description": "Delete a mailbox and the mail account behind it. **Every message it holds is erased**, and Sendly keeps no other copy — this is not recoverable from the dashboard or by support. Mail sent to the address afterwards is rejected.\n\nRequires an admin of the project.\n\nRequires the `mailboxes:write` scope — Create and delete mailboxes on your verified domains.",
        "operationId": "deleteMailbox",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "deleted": {
                          "enum": [
                            true
                          ],
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "deleted"
                      ],
                      "type": "object"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Mailbox deleted"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "mailboxes:write"
            ]
          }
        ],
        "summary": "Delete a mailbox",
        "tags": [
          "Mailboxes"
        ]
      },
      "get": {
        "description": "One mailbox, with the IMAP and SMTP host/port/username a mail client needs. The password is not included: mailbox credentials are app passwords, created separately and shown once.\n\nRequires the `mailboxes:read` scope — View the mailboxes on your domains and their settings.",
        "operationId": "getMailbox",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MailboxDetail"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Mailbox with connection settings"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "mailboxes:read"
            ]
          }
        ],
        "summary": "Get a mailbox",
        "tags": [
          "Mailboxes"
        ]
      }
    },
    "/api/mailboxes/{id}/app-passwords": {
      "get": {
        "description": "Every app password on the mailbox — name, protocols, last four characters and last use. The secrets themselves are stored hashed and are not retrievable here or anywhere else; a password you have lost is replaced, not recovered.\n\nRequires the `mailboxes:read` scope — View the mailboxes on your domains and their settings.",
        "operationId": "listAppPasswords",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "items": {
                        "$ref": "#/components/schemas/AppPassword"
                      },
                      "type": "array"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "App password list"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "mailboxes:read"
            ]
          }
        ],
        "summary": "List a mailbox's app passwords",
        "tags": [
          "Mailboxes"
        ]
      },
      "post": {
        "description": "Mint an IMAP/SMTP credential for the mailbox, so a mail client can connect to it.\n\n**The secret is not in the response.** A delegated caller receives `revealUrl` — a single-use link that shows the password once in a browser, to a signed-in project admin. The connection that created the password cannot open its own link, and the link is spent by the first attempt to open it, successful or not.\n\nThat is deliberate and not a limitation to work around: an app password is a live mail credential that a client authenticates with directly, it outlives the grant that created it, and it is revoked from a different screen. Returning it inline would place a working mail credential in an agent's context, its transcript, and every log that transcript reaches.\n\nRequires an admin of the project. An API key is refused with 401 — this endpoint needs a user, so use an OAuth connection.\n\nRequires the `mailboxes:write` scope — Create and delete mailboxes on your verified domains.",
        "operationId": "createAppPassword",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateAppPassword"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/AppPasswordReveal"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "App password created; the secret is behind the one-time link"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "mailboxes:write"
            ]
          }
        ],
        "summary": "Create an app password",
        "tags": [
          "Mailboxes"
        ]
      }
    },
    "/api/mailboxes/{id}/app-passwords/{passwordId}": {
      "delete": {
        "description": "Revoke one app password. Any mail client still configured with it stops authenticating immediately — there is no grace period — and the mailbox and its messages are untouched.\n\nRequires an admin of the project. An API key is refused with 401.\n\nRequires the `mailboxes:write` scope — Create and delete mailboxes on your verified domains.",
        "operationId": "revokeAppPassword",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "in": "path",
            "name": "passwordId",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "revoked": {
                          "enum": [
                            true
                          ],
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "revoked"
                      ],
                      "type": "object"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "App password revoked"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "mailboxes:write"
            ]
          }
        ],
        "summary": "Revoke an app password",
        "tags": [
          "Mailboxes"
        ]
      }
    },
    "/api/mailboxes/{id}/drafts": {
      "post": {
        "description": "Ask Sendly's assistant to write a message for a mailbox — a new email from a short brief, a rewrite of something you already have, or a set of subject lines.\n\n**It returns text and sends nothing.** The response always reports `sent: false`, and there is no argument that changes that. Putting a draft in someone's inbox is a separate operation (`sendMailboxMessage`) under a separate scope, so a client that may draft is not thereby a client that may mail your customers.\n\nThat is also why this operation asks only for `mailboxes:read`: it names a mailbox so the draft can be written in that address's voice, reads no correspondence, and stores nothing.\n\nEverything you pass — the brief, the draft, the recipient context — is treated strictly as data describing what to write, never as instructions to the model.\n\nDrafting is capped at 120 requests per hour per project.\n\nRequires the `mailboxes:read` scope — View the mailboxes on your domains and their settings.",
        "operationId": "draftMailboxMessage",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DraftMailboxMessage"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "body": {
                          "description": "Suggested plain-text body, or null.",
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "sent": {
                          "description": "Always false. Reported rather than assumed, so a draft cannot be mistaken for a send.",
                          "enum": [
                            false
                          ],
                          "type": "boolean"
                        },
                        "subject": {
                          "description": "Suggested subject, or null.",
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "subjects": {
                          "description": "Alternative subject lines (`subject` mode); empty otherwise.",
                          "items": {
                            "type": "string"
                          },
                          "type": "array"
                        }
                      },
                      "required": [
                        "subject",
                        "body",
                        "subjects",
                        "sent"
                      ],
                      "type": "object"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "A draft. Nothing was sent."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          },
          "502": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The drafting model was unreachable or returned nothing usable."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "mailboxes:read"
            ]
          }
        ],
        "summary": "Draft a message with AI",
        "tags": [
          "Mailboxes"
        ]
      }
    },
    "/api/mailboxes/{id}/messages": {
      "post": {
        "description": "Compose and send a NEW message from a hosted mailbox — a first email to someone, not a reply inside a thread that already exists. It goes out from the mailbox's own address, over its own domain, and the recipient can reply to it.\n\n**The sender is the mailbox in the path.** There is no `from` field: a route that sends under a customer's own identity must not take the identity as an argument.\n\n**The body is plain text.** Sendly renders the HTML part from it, escaping as it goes, so there is one place where text becomes markup and it is inside Sendly. HTML is not accepted.\n\nBcc recipients are delivered to but appear in no header, which also means the copy filed in the mailbox's Sent folder does not record them.\n\nRefusals worth handling by name:\n\n- `422 RECIPIENT_SUPPRESSED` — one or more recipients are on this project's suppression list. The message names them; remove them or take them off the list.\n- `422 CONTENT_REFUSED` — the outbound content scanner refused the message.\n- `503 CONTENT_SCAN_UNAVAILABLE` — screening could not reach a verdict for a young project. Nothing was sent; retry shortly.\n- `429` — a mailbox may send 60 messages an hour through this endpoint.\n\nThe message is stored as a new conversation on the mailbox, so the reply threads onto it.\n\nRequires the `mailboxes:send` scope — Write and send new email from your hosted mailboxes, as that address.",
        "operationId": "sendMailboxMessage",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ComposeMailboxMessage"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "conversationId": {
                          "description": "The conversation this send started. Replies thread onto it.",
                          "format": "uuid",
                          "type": "string"
                        },
                        "messageId": {
                          "description": "The stored outbound message.",
                          "format": "uuid",
                          "type": "string"
                        },
                        "submitted": {
                          "enum": [
                            true
                          ],
                          "type": "boolean"
                        }
                      },
                      "required": [
                        "submitted",
                        "conversationId",
                        "messageId"
                      ],
                      "type": "object"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Message submitted"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The mailbox is not active, a recipient is suppressed (`RECIPIENT_SUPPRESSED`), or the content scanner refused the message (`CONTENT_REFUSED`)."
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          },
          "502": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "The mail server refused the submission. Nothing was sent."
          },
          "503": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Message screening could not reach a verdict. Nothing was sent; retry shortly."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "mailboxes:send"
            ]
          }
        ],
        "summary": "Send a message from a mailbox",
        "tags": [
          "Mailboxes"
        ]
      }
    },
    "/api/projects/{id}/api-keys": {
      "get": {
        "description": "Returns every key on the project, revoked ones included — filter on `revokedAt` to show only live keys. Never returns a token or its hash: `lastFour` is the only fragment of the secret that survives creation.\n\nRequires the `api-keys:read` scope — See which API keys exist, including what each one is allowed to do.",
        "operationId": "listApiKeys",
        "parameters": [
          {
            "description": "Project id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Project id.",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyListResponse"
                }
              }
            },
            "description": "API key list"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "api-keys:read"
            ]
          }
        ],
        "summary": "List API keys for a project",
        "tags": [
          "API Keys"
        ]
      },
      "post": {
        "description": "Mint a new API key on the project. The token is NOT returned — the response carries the key's metadata plus a one-time `revealUrl` that only a signed-in dashboard session can open.\n\n**Scope attenuation:** a key created with a delegated credential (an OAuth token or another API key) may not carry a scope that credential does not itself hold. A request that asks for more is refused with `400 SCOPE_ESCALATION` rather than quietly narrowed, so the mistake is reported where it was made instead of surfacing later as an unexplained 403. Naming only `legacyGrantPreset` counts as asking for every scope that preset implies.\n\nRequires the `api-keys:write` scope — Create, rotate, and revoke API keys — these keep working even after you disconnect this app.",
        "operationId": "createApiKey",
        "parameters": [
          {
            "description": "Project id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Project id.",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateApiKeyBody"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/ApiKey"
                        },
                        {
                          "properties": {
                            "revealExpiresAt": {
                              "description": "When the reveal link stops working. Create or rotate again to get a new one.",
                              "format": "date-time",
                              "type": "string"
                            },
                            "revealUrl": {
                              "description": "A one-time, session-authenticated URL where the person who owns this project can see the secret. The secret itself is never returned to an API or agent caller: opening this link requires a signed-in dashboard session, so the credential that created the key cannot redeem it. Single use — the first successful open consumes it.",
                              "format": "uri",
                              "type": "string"
                            }
                          },
                          "required": [
                            "revealUrl",
                            "revealExpiresAt"
                          ],
                          "type": "object"
                        }
                      ],
                      "description": "An API key's metadata. Never carries the token or its hash — `lastFour` is the only surviving fragment of the secret once the key has been created."
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "API key created; the secret is behind the reveal link."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "api-keys:write"
            ]
          }
        ],
        "summary": "Create an API key",
        "tags": [
          "API Keys"
        ]
      }
    },
    "/api/projects/{id}/api-keys/{keyId}": {
      "delete": {
        "description": "Revoke an API key. Answers `{ success: true }` with no `data` key. 404 if the key does not exist under this project.\n\nRequires the `api-keys:write` scope — Create, rotate, and revoke API keys — these keep working even after you disconnect this app.",
        "operationId": "revokeApiKey",
        "parameters": [
          {
            "description": "Project id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Project id.",
              "type": "string"
            }
          },
          {
            "description": "API key id.",
            "in": "path",
            "name": "keyId",
            "required": true,
            "schema": {
              "description": "API key id.",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEmpty"
                }
              }
            },
            "description": "API key revoked"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "api-keys:write"
            ]
          }
        ],
        "summary": "Revoke an API key",
        "tags": [
          "API Keys"
        ]
      }
    },
    "/api/projects/{id}/api-keys/{keyId}/rotate": {
      "post": {
        "description": "Replace the key's secret in place, keeping its id, name and scopes. The PREVIOUS secret stops authenticating immediately — there is no overlap window — so anything still using it starts failing on its next request. As with creation, the new secret is not returned: the response carries `lastFour` and a one-time `revealUrl`. A revoked key cannot be rotated (`400 KEY_REVOKED`).\n\nRequires the `api-keys:write` scope — Create, rotate, and revoke API keys — these keep working even after you disconnect this app.",
        "operationId": "rotateApiKey",
        "parameters": [
          {
            "description": "Project id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Project id.",
              "type": "string"
            }
          },
          {
            "description": "API key id.",
            "in": "path",
            "name": "keyId",
            "required": true,
            "schema": {
              "description": "API key id.",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "properties": {
                        "lastFour": {
                          "type": "string"
                        },
                        "revealExpiresAt": {
                          "description": "When the reveal link stops working. Create or rotate again to get a new one.",
                          "format": "date-time",
                          "type": "string"
                        },
                        "revealUrl": {
                          "description": "A one-time, session-authenticated URL where the person who owns this project can see the secret. The secret itself is never returned to an API or agent caller: opening this link requires a signed-in dashboard session, so the credential that created the key cannot redeem it. Single use — the first successful open consumes it.",
                          "format": "uri",
                          "type": "string"
                        }
                      },
                      "required": [
                        "lastFour",
                        "revealUrl",
                        "revealExpiresAt"
                      ],
                      "type": "object"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "API key rotated; the new secret is behind the reveal link."
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "api-keys:write"
            ]
          }
        ],
        "summary": "Rotate an API key's secret",
        "tags": [
          "API Keys"
        ]
      }
    },
    "/api/snippets": {
      "get": {
        "description": "Cursor-paginated list of the project's reusable template fragments. `search` matches name and description.\n\nRequires the `templates:read` scope — View your email templates.",
        "operationId": "listSnippets",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "search",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SnippetListResponse"
                }
              }
            },
            "description": "Snippet list"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation failed — request body or query parameters did not match the schema"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "templates:read"
            ]
          }
        ],
        "summary": "List snippets",
        "tags": [
          "Templates"
        ]
      },
      "post": {
        "description": "`name` is the literal identifier templates include with `{{> name}}`: it must start with a letter and contain only letters, digits, hyphen or underscore, and it is unique within the project.\n\nRequires the `templates:write` scope — Create, edit, and delete your email templates.",
        "operationId": "createSnippet",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateSnippet"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Snippet"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Snippet created"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "A snippet with that name already exists in this project"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation failed — request body or query parameters did not match the schema"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          }
        ],
        "summary": "Create a snippet",
        "tags": [
          "Templates"
        ]
      }
    },
    "/api/snippets/{id}": {
      "delete": {
        "description": "Templates that still include the snippet keep rendering — an absent snippet renders as an empty string, exactly like an absent variable.\n\nRequires the `templates:write` scope — Create, edit, and delete your email templates.",
        "operationId": "deleteSnippet",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdResponse"
                }
              }
            },
            "description": "Snippet deleted"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          }
        ],
        "summary": "Delete a snippet",
        "tags": [
          "Templates"
        ]
      },
      "get": {
        "description": "Requires the `templates:read` scope — View your email templates.",
        "operationId": "getSnippet",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Snippet"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Snippet"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "templates:read"
            ]
          }
        ],
        "summary": "Get a snippet",
        "tags": [
          "Templates"
        ]
      },
      "patch": {
        "description": "Requires the `templates:write` scope — Create, edit, and delete your email templates.",
        "operationId": "updateSnippet",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateSnippet"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Snippet"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Updated snippet"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "A snippet with that name already exists in this project"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation failed — request body or query parameters did not match the schema"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          }
        ],
        "summary": "Update a snippet",
        "tags": [
          "Templates"
        ]
      }
    },
    "/api/suppression": {
      "get": {
        "description": "Cursor-paginated list of suppressed addresses. Filter by `reason`.\n\nRequires the `suppression:read` scope — View the addresses on your suppression list.",
        "operationId": "listSuppressions",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "reason",
            "required": false,
            "schema": {
              "enum": [
                "HARD_BOUNCE",
                "COMPLAINT",
                "MANUAL",
                "UNSUBSCRIBE"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuppressionListResponse"
                }
              }
            },
            "description": "Suppression list"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "suppression:read"
            ]
          }
        ],
        "summary": "List suppressed emails",
        "tags": [
          "Suppression"
        ]
      },
      "post": {
        "description": "The `source` field is auto-derived: `API` for API-key callers, `DASHBOARD` for session callers.\n\nRequires the `suppression:write` scope — Add and remove addresses on your suppression list.",
        "operationId": "addSuppression",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddSuppression"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Suppression"
                }
              }
            },
            "description": "Suppression added"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "suppression:write"
            ]
          }
        ],
        "summary": "Manually add an email to the suppression list",
        "tags": [
          "Suppression"
        ]
      }
    },
    "/api/suppression/{email}": {
      "delete": {
        "description": "Idempotent. Silently no-ops if the suppression doesn't exist.\n\nRequires the `suppression:write` scope — Add and remove addresses on your suppression list.",
        "operationId": "removeSuppression",
        "parameters": [
          {
            "description": "URL-encoded email address",
            "in": "path",
            "name": "email",
            "required": true,
            "schema": {
              "description": "URL-encoded email address",
              "type": "string"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Suppression removed"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "suppression:write"
            ]
          }
        ],
        "summary": "Remove an email from the suppression list",
        "tags": [
          "Suppression"
        ]
      },
      "get": {
        "description": "Returns `{ suppressed, reason?, source?, createdAt? }`. The path parameter must be URL-encoded.\n\nRequires the `suppression:read` scope — View the addresses on your suppression list.",
        "operationId": "checkSuppression",
        "parameters": [
          {
            "description": "URL-encoded email address",
            "in": "path",
            "name": "email",
            "required": true,
            "schema": {
              "description": "URL-encoded email address",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuppressionCheckResponse"
                }
              }
            },
            "description": "Suppression check result"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "suppression:read"
            ]
          }
        ],
        "summary": "Check whether an email is suppressed",
        "tags": [
          "Suppression"
        ]
      }
    },
    "/api/templates": {
      "get": {
        "description": "Cursor-paginated list of templates. Use `search` for full-text-ish filtering on name/description/subject.\n\nRequires the `templates:read` scope — View your email templates.",
        "operationId": "listTemplates",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "search",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "emailCategory",
            "required": false,
            "schema": {
              "enum": [
                "MARKETING",
                "TRANSACTIONAL",
                "SELF_MANAGED_UNSUBSCRIBE"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateListResponse"
                }
              }
            },
            "description": "Template list"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation failed — request body or query parameters did not match the schema"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "templates:read"
            ]
          }
        ],
        "summary": "List templates",
        "tags": [
          "Templates"
        ]
      },
      "post": {
        "description": "Create a new email template. The `from` domain must already be verified for the project.\n\nRequires the `templates:write` scope — Create, edit, and delete your email templates.",
        "operationId": "createTemplate",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateTemplate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Template"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Template created"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation failed — request body or query parameters did not match the schema"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          }
        ],
        "summary": "Create a template",
        "tags": [
          "Templates"
        ]
      }
    },
    "/api/templates/{id}": {
      "delete": {
        "description": "Answers 200 with `{ success, data: { id } }` (pre-seam this was 204 No Content). Refuses with 409 if the template is still attached to a workflow step or active campaign.\n\nRequires the `templates:write` scope — Create, edit, and delete your email templates.",
        "operationId": "deleteTemplate",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/IdResponse"
                }
              }
            },
            "description": "Template deleted"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "409": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Template still in use"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          }
        ],
        "summary": "Delete a template",
        "tags": [
          "Templates"
        ]
      },
      "get": {
        "description": "Requires the `templates:read` scope — View your email templates.",
        "operationId": "getTemplate",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Template"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Template"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "templates:read"
            ]
          }
        ],
        "summary": "Get a template",
        "tags": [
          "Templates"
        ]
      },
      "patch": {
        "description": "Update one or more fields. If `from` changes, the new domain must already be verified.\n\nRequires the `templates:write` scope — Create, edit, and delete your email templates.",
        "operationId": "updateTemplate",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateTemplate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Template"
                    },
                    "success": {
                      "enum": [
                        true
                      ],
                      "type": "boolean"
                    }
                  },
                  "required": [
                    "success",
                    "data"
                  ],
                  "type": "object"
                }
              }
            },
            "description": "Updated template"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation failed — request body or query parameters did not match the schema"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          }
        ],
        "summary": "Update a template",
        "tags": [
          "Templates"
        ]
      }
    },
    "/api/track": {
      "post": {
        "description": "Record a custom event, creating or updating the contact by email as a side effect. Requires a FULL (`sk_*`) key — SENDING_ONLY (`pk_*`) keys answer 403, since recording events is not sending mail. Reserved system event names (`email.*`, `contact.subscribed`/`unsubscribed`, `segment.*.entry`/`.exit`) are rejected.\n\nRequires the `events:write` scope — Record custom events for your contacts.",
        "operationId": "trackEvent",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TrackEvent"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TrackEventResponse"
                }
              }
            },
            "description": "Event tracked"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "OAuth2": [
              "events:write"
            ]
          }
        ],
        "summary": "Track a custom event for a contact",
        "tags": [
          "Events"
        ]
      }
    },
    "/api/users/me/projects": {
      "post": {
        "description": "Create a new project owned by the authenticated user. Answers the raw project row directly — no `{ success, data }` envelope — with status 201.\n\nPreconditions the route enforces before writing: the caller's email must be verified, the caller must be under their cap on active (non-disabled) owned projects, and the call is rate-limited per user.\n\nRequires the `projects:write` scope — Create new projects on your account.",
        "operationId": "createProject",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "name": {
                    "maxLength": 100,
                    "minLength": 1,
                    "type": "string"
                  },
                  "sesRegion": {
                    "description": "AWS SES region for the project. Once a domain is added the region is locked and cannot be changed.",
                    "enum": [
                      "us-east-1",
                      "us-west-2",
                      "eu-west-1"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "name"
                ],
                "type": "object"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectRecord"
                }
              }
            },
            "description": "Project created"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "422": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation failed — request body or query parameters did not match the schema"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "projects:write"
            ]
          }
        ],
        "summary": "Create a project",
        "tags": [
          "Projects"
        ]
      }
    },
    "/api/v1/analytics/campaigns": {
      "get": {
        "description": "Campaign counts plus average open and click rates.\n\n`total` and `active` count campaigns CREATED in the window; `completed` counts campaigns SENT in it — so a campaign created earlier and sent inside the window appears only in `completed`. Rates are percentages to one decimal place, averaged over the campaigns sent in the window.\n\n`from` defaults to 30 days ago and is clamped to at most 90 days back; `to` defaults to now. A wider request is narrowed rather than refused, and the `window` field states the range actually covered — read it before comparing two responses.\n\nRequires the `analytics:read` scope — View your sending analytics and engagement metrics.",
        "operationId": "v1GetCampaignAnalytics",
        "parameters": [
          {
            "description": "Start of the window (ISO 8601). Defaults to 30 days ago; clamped to at most 90 days back.",
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "description": "Start of the window (ISO 8601). Defaults to 30 days ago; clamped to at most 90 days back.",
              "format": "date-time",
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "description": "End of the window (ISO 8601). Defaults to now.",
            "in": "query",
            "name": "to",
            "required": false,
            "schema": {
              "description": "End of the window (ISO 8601). Defaults to now.",
              "format": "date-time",
              "type": [
                "string",
                "null"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsCampaignStatsV1"
                }
              }
            },
            "description": "Campaign statistics"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "analytics:read"
            ]
          }
        ],
        "summary": "Retrieve campaign totals and engagement",
        "tags": [
          "Analytics"
        ]
      }
    },
    "/api/v1/analytics/timeseries": {
      "get": {
        "description": "Daily counts of emails created, delivered, opened, clicked and bounced. Every day in the window is present even with zero activity, so the series never needs gap-filling.\n\n`from` defaults to 30 days ago and is clamped to at most 90 days back; `to` defaults to now. A wider request is narrowed rather than refused, and the `window` field states the range actually covered — read it before comparing two responses.\n\nRequires the `analytics:read` scope — View your sending analytics and engagement metrics.",
        "operationId": "v1GetAnalyticsTimeseries",
        "parameters": [
          {
            "description": "Start of the window (ISO 8601). Defaults to 30 days ago; clamped to at most 90 days back.",
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "description": "Start of the window (ISO 8601). Defaults to 30 days ago; clamped to at most 90 days back.",
              "format": "date-time",
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "description": "End of the window (ISO 8601). Defaults to now.",
            "in": "query",
            "name": "to",
            "required": false,
            "schema": {
              "description": "End of the window (ISO 8601). Defaults to now.",
              "format": "date-time",
              "type": [
                "string",
                "null"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsTimeseriesV1"
                }
              }
            },
            "description": "Daily time series"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "analytics:read"
            ]
          }
        ],
        "summary": "Retrieve the daily email time series",
        "tags": [
          "Analytics"
        ]
      }
    },
    "/api/v1/analytics/top-campaigns": {
      "get": {
        "description": "Campaigns sent in the window, ranked by open rate, capped at 50 rows. Not cursor-paginated: a leaderboard is a top-N by definition, and paging one would mean re-ranking on every page.\n\n`from` defaults to 30 days ago and is clamped to at most 90 days back; `to` defaults to now. A wider request is narrowed rather than refused, and the `window` field states the range actually covered — read it before comparing two responses.\n\nRequires the `analytics:read` scope — View your sending analytics and engagement metrics.",
        "operationId": "v1ListTopCampaigns",
        "parameters": [
          {
            "description": "Start of the window (ISO 8601). Defaults to 30 days ago; clamped to at most 90 days back.",
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "description": "Start of the window (ISO 8601). Defaults to 30 days ago; clamped to at most 90 days back.",
              "format": "date-time",
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "description": "End of the window (ISO 8601). Defaults to now.",
            "in": "query",
            "name": "to",
            "required": false,
            "schema": {
              "description": "End of the window (ISO 8601). Defaults to now.",
              "format": "date-time",
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 10,
              "maximum": 50,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsTopCampaignsV1"
                }
              }
            },
            "description": "Ranked campaigns"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "analytics:read"
            ]
          }
        ],
        "summary": "List the best-performing campaigns",
        "tags": [
          "Analytics"
        ]
      }
    },
    "/api/v1/campaigns": {
      "get": {
        "description": "Cursor-paginated list of campaigns, newest first. Pass the previous response's `next_cursor` as `after` to page forward; `has_more` is false and `next_cursor` is null on the last page.\n\nUnlike the legacy `/api/*` endpoints, v1 responses are the bare payload (no `{ success, data }` envelope) and errors are RFC 9457 problem documents.\n\nRequires the `campaigns:read` scope — View your campaigns and their performance.",
        "operationId": "v1ListCampaigns",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "description": "Opaque cursor from a previous response's `next_cursor`.",
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignV1List"
                }
              }
            },
            "description": "Campaign list"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "campaigns:read"
            ]
          }
        ],
        "summary": "List campaigns",
        "tags": [
          "Campaigns"
        ]
      },
      "post": {
        "description": "Create a campaign in `DRAFT`. Creating never sends — `POST /api/v1/campaigns/{id}/send` is the only operation that puts mail on the wire — so a campaign can be built up and reviewed before it costs anything.\n\nThe `from` address is checked against this project's verified domains before the campaign is written, so a campaign never exists with a sender it cannot use.\n\n`segment_id` is required when `audience_type` is `SEGMENT`; `audience_condition` is required when it is `FILTERED`. Both answer 422 when missing.\n\nRequires the `campaigns:write` scope — Create, edit, and organize your campaigns.",
        "operationId": "v1CreateCampaign",
        "parameters": [
          {
            "description": "Replay-safety key (24h TTL). Reuse it only to retry the identical request.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "description": "Replay-safety key (24h TTL). Reuse it only to retry the identical request.",
              "maxLength": 255,
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignV1Create"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignV1"
                }
              }
            },
            "description": "Campaign created"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — `segment_id` names a segment that does not belong to this project."
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`conflict` — a request with this `Idempotency-Key` is still in flight. Retry shortly."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — the request did not match the schema; or `idempotency_key_reused` — this `Idempotency-Key` was already spent on a request with a different body. A key names ONE request, so it is never silently served another one's result: send a new key."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "campaigns:write"
            ]
          }
        ],
        "summary": "Create a campaign",
        "tags": [
          "Campaigns"
        ]
      }
    },
    "/api/v1/campaigns/{id}": {
      "delete": {
        "description": "Delete a `DRAFT` campaign. A campaign that has sent is the record of what went out and cannot be deleted (400) — cancel it instead if it is still scheduled or in flight.\n\nRequires the `campaigns:write` scope — Create, edit, and organize your campaigns.",
        "operationId": "v1DeleteCampaign",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignV1Deleted"
                }
              }
            },
            "description": "Campaign deleted"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — only `DRAFT` campaigns can be deleted."
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no campaign with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "campaigns:write"
            ]
          }
        ],
        "summary": "Delete a campaign",
        "tags": [
          "Campaigns"
        ]
      },
      "get": {
        "description": "Fetch one campaign, including its materialized delivery counters.\n\nRequires the `campaigns:read` scope — View your campaigns and their performance.",
        "operationId": "v1GetCampaign",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignV1"
                }
              }
            },
            "description": "The campaign"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no campaign with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "campaigns:read"
            ]
          }
        ],
        "summary": "Retrieve a campaign",
        "tags": [
          "Campaigns"
        ]
      },
      "patch": {
        "description": "Partial update: an omitted field is left untouched. Only `DRAFT` and `SCHEDULED` campaigns are editable — editing one that is already sending would change what half its recipients receive, so it answers 400.\n\nChanging `from` re-verifies the sender domain. Changing the audience on a `DRAFT` campaign schedules a recipient recount, so `stats.total_recipients` converges shortly after the response.\n\nRequires the `campaigns:write` scope — Create, edit, and organize your campaigns.",
        "operationId": "v1UpdateCampaign",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignV1Update"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignV1"
                }
              }
            },
            "description": "The updated campaign"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — the campaign is not in an editable status, or the segment change is not allowed."
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no campaign with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "campaigns:write"
            ]
          }
        ],
        "summary": "Update a campaign",
        "tags": [
          "Campaigns"
        ]
      }
    },
    "/api/v1/campaigns/{id}/cancel": {
      "post": {
        "description": "Cancel a `SCHEDULED`, `SENDING`, or `PAUSED` campaign. Terminal — a cancelled campaign cannot be resumed or re-sent. Takes no request body.\n\nLike every action on this resource, the response is the campaign itself, so `status` tells you what the transition did without a second request.\n\nRequires the `campaigns:write` scope — Create, edit, and organize your campaigns.",
        "operationId": "v1CancelCampaign",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignV1"
                }
              }
            },
            "description": "The cancelled campaign"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — only `SCHEDULED`, `SENDING`, or `PAUSED` campaigns can be cancelled."
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no campaign with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "campaigns:write"
            ]
          }
        ],
        "summary": "Cancel a campaign",
        "tags": [
          "Campaigns"
        ]
      }
    },
    "/api/v1/campaigns/{id}/failures": {
      "get": {
        "description": "The recipients this campaign did not reach, read from its per-contact send ledger. The campaign counters say how many were sent; only this says WHO was dropped and why, which is what makes retrying a decision rather than a guess.\n\n`reason` comes from a fixed vocabulary, not from the underlying error text, so it is stable enough to branch on. It is `null` for rows recorded before reasons were captured.\n\nCursor-paginated like every other v1 list. Unlike them it also returns `total`: the retry action operates on that number, and a page that can only say `has_more` cannot tell you whether 3 or 30,000 sends failed.\n\nRequires the `campaigns:read` scope — View your campaigns and their performance.",
        "operationId": "v1ListCampaignFailures",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "description": "Opaque cursor from a previous response's `next_cursor`.",
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignV1FailureList"
                }
              }
            },
            "description": "Failed sends"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no campaign with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "campaigns:read"
            ]
          }
        ],
        "summary": "List a campaign's failed sends",
        "tags": [
          "Campaigns"
        ]
      }
    },
    "/api/v1/campaigns/{id}/pause": {
      "post": {
        "description": "Pause a `SENDING` campaign. The workers check the flag between batches, so a small number of already-queued emails may still be delivered after this returns. Takes no request body.\n\nRequires the `campaigns:write` scope — Create, edit, and organize your campaigns.",
        "operationId": "v1PauseCampaign",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignV1"
                }
              }
            },
            "description": "The paused campaign"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — only a `SENDING` campaign can be paused."
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no campaign with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "campaigns:write"
            ]
          }
        ],
        "summary": "Pause a sending campaign",
        "tags": [
          "Campaigns"
        ]
      }
    },
    "/api/v1/campaigns/{id}/resume": {
      "post": {
        "description": "Resume a `PAUSED` campaign from the last entry in its per-contact send ledger. That ledger also dedupes, so a contact already sent to is skipped rather than mailed twice. Takes no request body.\n\nRequires the `campaigns:write` scope — Create, edit, and organize your campaigns.",
        "operationId": "v1ResumeCampaign",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignV1"
                }
              }
            },
            "description": "The resumed campaign"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — only a `PAUSED` campaign can be resumed."
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no campaign with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "campaigns:write"
            ]
          }
        ],
        "summary": "Resume a paused campaign",
        "tags": [
          "Campaigns"
        ]
      }
    },
    "/api/v1/campaigns/{id}/retry-failed": {
      "post": {
        "description": "Re-drive only the recipients whose send failed, through the same pipeline the campaign used. Nobody who was already mailed is mailed again: each ledger row is claimed before it is touched, and a row whose email was created before the failure was recorded is re-queued rather than re-sent.\n\nThe retry runs in the background, so this returns as soon as it is queued, with the number of rows it was queued for. Takes no request body.\n\nOnly a `SENT` campaign can be retried: a `SENDING` or `PAUSED` one still has its own send in progress against the same ledger, and a `CANCELLED` one was stopped deliberately.\n\nRequires the `campaigns:write` scope — Create, edit, and organize your campaigns.",
        "operationId": "v1RetryCampaignFailures",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignV1RetryFailed"
                }
              }
            },
            "description": "The retry was queued"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — only a `SENT` campaign can have its failed sends retried."
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no campaign with this id belongs to the authenticated project."
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`conflict` — a retry is already running for this campaign."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "campaigns:write"
            ]
          }
        ],
        "summary": "Retry a campaign's failed sends",
        "tags": [
          "Campaigns"
        ]
      }
    },
    "/api/v1/campaigns/{id}/send": {
      "post": {
        "description": "Start sending immediately, or park the campaign in `SCHEDULED` by passing `scheduled_for` (which must be in the future). The request body may be omitted entirely to send now.\n\n**Send an `Idempotency-Key`.** This is the operation that cannot be undone: a retry without one starts a second fan-out over the same audience. The key is scoped to this campaign, so the same key on a different campaign is a `422 idempotency_key_reused` rather than a replay of the first campaign's response.\n\nAnswers 400 when the campaign is not `DRAFT`/`SCHEDULED` or has no recipients, and 403 when the send would exceed the project's billing limit.\n\nRequires the `campaigns:send` scope — Send or schedule your campaigns to their audience.",
        "operationId": "v1SendCampaign",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Replay-safety key (24h TTL). Reuse it only to retry the identical request.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "description": "Replay-safety key (24h TTL). Reuse it only to retry the identical request.",
              "maxLength": 255,
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CampaignV1Send"
              }
            }
          },
          "required": false
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignV1"
                }
              }
            },
            "description": "The campaign, now `SENDING` or `SCHEDULED`"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — the campaign has already been sent or is sending, has no recipients, or `scheduled_for` is not in the future."
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no campaign with this id belongs to the authenticated project."
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`conflict` — a request with this `Idempotency-Key` is still in flight. Retry shortly."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — the request did not match the schema; or `idempotency_key_reused` — this `Idempotency-Key` was already spent on a request with a different body. A key names ONE request, so it is never silently served another one's result: send a new key."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "campaigns:send"
            ]
          }
        ],
        "summary": "Send or schedule a campaign",
        "tags": [
          "Campaigns"
        ]
      }
    },
    "/api/v1/campaigns/{id}/stats": {
      "get": {
        "description": "Delivery and engagement counters for one campaign, plus the rates derived from them. Every number is a materialized counter on the campaign row, so this is a single indexed read regardless of how many emails the campaign sent — cheap enough to poll while a campaign is in flight.\n\nRates are percentages (0–100) against `sent`, and are 0 before anything has been sent.\n\nRequires the `campaigns:read` scope — View your campaigns and their performance.",
        "operationId": "v1GetCampaignStats",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CampaignV1Stats"
                }
              }
            },
            "description": "Campaign statistics"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no campaign with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "campaigns:read"
            ]
          }
        ],
        "summary": "Retrieve campaign statistics",
        "tags": [
          "Campaigns"
        ]
      }
    },
    "/api/v1/contacts": {
      "get": {
        "description": "Cursor-paginated list of contacts, newest first. Pass the previous response's `next_cursor` as `after` to page forward; `has_more` is false and `next_cursor` is null on the last page.\n\nA cursor is bound to the filters that minted it. Changing `search` or `subscribed` while reusing a cursor answers `422 validation_error` rather than returning a page that belongs to neither query — drop the cursor and start again from the first page.\n\nRequires the `contacts:read` scope — View your contacts and their custom fields.",
        "operationId": "v1ListContacts",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "description": "Opaque cursor from a previous response's `next_cursor`.",
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "description": "Case-insensitive substring match on the email address.",
            "in": "query",
            "name": "search",
            "required": false,
            "schema": {
              "description": "Case-insensitive substring match on the email address.",
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "description": "Filter to subscribed (`true`) or unsubscribed (`false`) contacts. Omit for both.",
            "in": "query",
            "name": "subscribed",
            "required": false,
            "schema": {
              "description": "Filter to subscribed (`true`) or unsubscribed (`false`) contacts. Omit for both.",
              "enum": [
                "true",
                "false"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactV1List"
                }
              }
            },
            "description": "Contact list"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "contacts:read"
            ]
          }
        ],
        "summary": "List contacts",
        "tags": [
          "Contacts"
        ]
      },
      "post": {
        "description": "Create a contact. The address is unique per project, so creating one that already exists answers `409 conflict` rather than updating the existing row — there is no upsert on this surface, because a silent update is not what a caller who wrote `create` asked for.\n\nRequires the `contacts:write` scope — Create, update, and delete your contacts.",
        "operationId": "v1CreateContact",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactV1Create"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactV1"
                }
              }
            },
            "description": "The created contact"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`conflict` — a contact with this email already exists in this project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "contacts:write"
            ]
          }
        ],
        "summary": "Create a contact",
        "tags": [
          "Contacts"
        ]
      }
    },
    "/api/v1/contacts/{id}": {
      "delete": {
        "description": "Delete the contact row. The emails already sent to that address are NOT erased — a send is a record of something that happened, and removing the recipient does not undo it. Erasing delivery history is a separate, irreversible operation that this surface deliberately does not expose.\n\nRequires the `contacts:write` scope — Create, update, and delete your contacts.",
        "operationId": "v1DeleteContact",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactV1Deleted"
                }
              }
            },
            "description": "Contact deleted"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no contact with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "contacts:write"
            ]
          }
        ],
        "summary": "Delete a contact",
        "tags": [
          "Contacts"
        ]
      },
      "get": {
        "description": "Fetch one contact by id.\n\nRequires the `contacts:read` scope — View your contacts and their custom fields.",
        "operationId": "v1GetContact",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactV1"
                }
              }
            },
            "description": "The contact"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no contact with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "contacts:read"
            ]
          }
        ],
        "summary": "Retrieve a contact",
        "tags": [
          "Contacts"
        ]
      },
      "patch": {
        "description": "Partial update. Omitted fields are left alone.\n\n`email` is NOT writable: an address is the contact's identity on this API, and rewriting it in place would silently change what every earlier send was addressed to. Create the new address instead.\n\n`custom_fields` REPLACES the stored object rather than merging into it, so a key you omit is gone. Read the contact first if you mean to change one key and keep the rest.\n\nRequires the `contacts:write` scope — Create, update, and delete your contacts.",
        "operationId": "v1UpdateContact",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactV1Update"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactV1"
                }
              }
            },
            "description": "The updated contact"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no contact with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "contacts:write"
            ]
          }
        ],
        "summary": "Update a contact",
        "tags": [
          "Contacts"
        ]
      }
    },
    "/api/v1/contacts/{id}/topics": {
      "get": {
        "description": "Everything this contact has said they want, as the send path reads it.\n\n`subscribed` on each topic is the EFFECTIVE answer: a contact who has never answered has no row at all, and the topic's `default_opt_in` decides what that silence means. It is folded in here so no caller has to reimplement the rule.\n\nThe top-level `subscribed` is the global marketing opt-out, and it OUTRANKS every topic. A caller reading only the topic list would conclude somebody is reachable when they are not.\n\nRequires the `topics:read` scope — View the topics you mail about and who is subscribed to each.",
        "operationId": "v1GetContactTopicPreferences",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactTopicPreferencesV1"
                }
              }
            },
            "description": "The contact's preferences"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no contact with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "topics:read"
            ]
          }
        ],
        "summary": "Get a contact's topic preferences",
        "tags": [
          "Topics"
        ]
      }
    },
    "/api/v1/deliverability/diagnose": {
      "get": {
        "description": "Reads the sending domain's DNS identity, the project's delivery outcomes over the last `window_days`, and — when an `address` is given — that recipient's suppression state, then publishes `findings`: what is actually wrong, worst first, each with a stable `code` and the fix. Branch on `code`, never on the prose.\n\nEverything here is read from data the platform already holds. The DNS statuses are the cached results of the verification refresh job, NOT a live lookup — `identity.last_checked_at` says when they were filled, and a domain that has never been checked reports nulls with a `dns_never_checked` finding rather than failures.\n\n`recent_delivery` is PROJECT-WIDE, not per-domain, because an email row records no sending domain; the field says so in its own `scope`. Rates are suppressed as meaningless below 20 sends in the window.\n\nRequires the `deliverability:read` scope — Check why mail from one of your domains is not arriving.",
        "operationId": "v1DiagnoseDeliverability",
        "parameters": [
          {
            "description": "A sending domain in this project, e.g. `example.com`.",
            "in": "query",
            "name": "domain",
            "required": true,
            "schema": {
              "description": "A sending domain in this project, e.g. `example.com`.",
              "maxLength": 253,
              "minLength": 3,
              "type": "string"
            }
          },
          {
            "description": "Optionally, one RECIPIENT address to check as well. Adds its suppression state — the single most common reason a specific person stops receiving mail while everyone else still does.",
            "in": "query",
            "name": "address",
            "required": false,
            "schema": {
              "description": "Optionally, one RECIPIENT address to check as well. Adds its suppression state — the single most common reason a specific person stops receiving mail while everyone else still does.",
              "format": "email",
              "type": "string"
            }
          },
          {
            "description": "How far back the delivery counters look. 1–30 days; defaults to 7.",
            "in": "query",
            "name": "window_days",
            "required": false,
            "schema": {
              "description": "How far back the delivery counters look. 1–30 days; defaults to 7.",
              "maximum": 30,
              "minimum": 1,
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeliverabilityDiagnosisV1"
                }
              }
            },
            "description": "The diagnosis, with findings"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "deliverability:read"
            ]
          }
        ],
        "summary": "Diagnose why mail from a domain is not arriving",
        "tags": [
          "Deliverability"
        ]
      }
    },
    "/api/v1/deliverability/dmarc": {
      "get": {
        "description": "DMARC aggregate (RUA) reports receiving providers have sent about your verified domains, newest reporting window first.\n\nThe only signal on this surface that does not come from us. A report is a receiver saying what it saw arrive claiming to be your domain, from every source — which is how a sender finds out both that their own alignment is broken and that somebody else is sending as them.\n\n`pass_count` counts DMARC ALIGNMENT from `policy_evaluated`, not raw authentication results: a message can pass SPF for a domain that is not the one in its From header, and that is precisely the case DMARC exists to catch.\n\nOnly reports about a domain registered in this project are stored, so a report about a domain you have not added will not appear here.\n\nRequires the `deliverability:read` scope — Check why mail from one of your domains is not arriving.",
        "operationId": "v1ListDmarcReports",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "description": "Opaque cursor from a previous response's `next_cursor`.",
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "description": "How far back to read, by the report's window start. 1-180 days; defaults to 30.",
            "in": "query",
            "name": "days",
            "required": false,
            "schema": {
              "description": "How far back to read, by the report's window start. 1-180 days; defaults to 30.",
              "maximum": 180,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Restrict to reports about one of your domains.",
            "in": "query",
            "name": "domain",
            "required": false,
            "schema": {
              "description": "Restrict to reports about one of your domains.",
              "maxLength": 253,
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DmarcReportV1List"
                }
              }
            },
            "description": "Cursor-paginated DMARC aggregate reports"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "deliverability:read"
            ]
          }
        ],
        "summary": "DMARC aggregate reports for your domains",
        "tags": [
          "Deliverability"
        ]
      }
    },
    "/api/v1/deliverability/domains": {
      "get": {
        "description": "Sent, delivered, bounced, complained and opened counts split by the RECIPIENT's domain and by UTC day, newest day first.\n\nThis is the axis `diagnose` cannot report: its `recent_delivery` is project-wide, because an email row records no sending domain. A project-wide bounce rate hides the case that matters most — one recipient domain refusing almost everything while the rest is healthy.\n\nThe counts are maintained by an hourly job over a rolling 30-day window, NOT computed on request; `computed_at` on each row says when it was last rebuilt. No rate is published: a rate over three sends is not information, and the counts let you apply your own threshold.\n\nRequires the `deliverability:read` scope — Check why mail from one of your domains is not arriving.",
        "operationId": "v1ListRecipientDomainStats",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "description": "Opaque cursor from a previous response's `next_cursor`.",
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "description": "How far back to read. 1-30 days; defaults to 30, which is the window the job maintains.",
            "in": "query",
            "name": "days",
            "required": false,
            "schema": {
              "description": "How far back to read. 1-30 days; defaults to 30, which is the window the job maintains.",
              "maximum": 30,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Restrict to one recipient domain.",
            "in": "query",
            "name": "domain",
            "required": false,
            "schema": {
              "description": "Restrict to one recipient domain.",
              "maxLength": 253,
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RecipientDomainStatsV1List"
                }
              }
            },
            "description": "Cursor-paginated recipient-domain rollup"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "deliverability:read"
            ]
          }
        ],
        "summary": "Delivery outcomes per recipient domain",
        "tags": [
          "Deliverability"
        ]
      }
    },
    "/api/v1/domains": {
      "get": {
        "description": "Cursor-paginated list of sending domains, newest first. Pass the previous response's `next_cursor` as `after` to page forward; `has_more` is false and `next_cursor` is null on the last page.\n\n`verified` is SES's verdict on the identity and is the field that decides whether mail can leave from this domain. `dkim_verified` is a separate fact — what the DNS health refresh last read for the DKIM records — and the two disagree while a re-check is in flight, so do not treat either as a spelling of the other.\n\nRequires the `domains:read` scope — View your sending domains and their verification status.",
        "operationId": "v1ListDomains",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "description": "Opaque cursor from a previous response's `next_cursor`.",
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DomainV1List"
                }
              }
            },
            "description": "Sending domain list"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "domains:read"
            ]
          }
        ],
        "summary": "List sending domains",
        "tags": [
          "Domains"
        ]
      },
      "post": {
        "description": "Register a domain and start SES DKIM verification. The response carries the identity as created — `verified` is false, because nothing is verified until the DKIM records are published in the domain's DNS and SES resolves them. Poll `/api/v1/domains/{id}/verify` after publishing them.\n\n`region` pins the SES region. The first domain a project adds LOCKS the project to that region and every later domain must match it — a project split across regions would have its SES configuration diverge silently.\n\n`stream` assigns the identity to transactional or marketing traffic at creation; omit it to leave the identity serving both. `stream_default` makes it the project's default for that stream and therefore requires `stream` — sending it alone answers `422 validation_error` rather than being ignored.\n\nA host already registered — to this project or to another — answers `409 conflict` when the caller can already send from it and `403 project_access_denied` when it belongs elsewhere. A subdomain whose registrable root is held by a suspended project is refused the same way.\n\nRequires the `domains:write` scope — Add and remove sending domains, and trigger verification.",
        "operationId": "v1CreateDomain",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/DomainV1Create"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DomainV1"
                }
              }
            },
            "description": "The registered sending domain, awaiting DNS"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`conflict` — this domain is already registered to a project you can send from."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          },
          "502": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error` — AWS SES rejected the identity setup (unusable credentials, a missing IAM permission, or a refusal on Amazon's side). No domain row is written, and the request is not at fault: retrying it unchanged will not help."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "domains:write"
            ]
          }
        ],
        "summary": "Add a sending domain",
        "tags": [
          "Domains"
        ]
      }
    },
    "/api/v1/domains/{id}": {
      "delete": {
        "description": "Remove the domain from the project. Refused with `409 conflict` while a template, workflow step or active campaign still sends from an address on this host — repoint those first, or their sends would start failing at SES with nothing here explaining why.\n\nThe underlying SES identity is dropped too, unless another project still holds the same host. Its DKIM keys go with it, so re-adding the domain later mints new records that have to be published again.\n\nRequires the `domains:write` scope — Add and remove sending domains, and trigger verification.",
        "operationId": "v1DeleteDomain",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DomainV1Deleted"
                }
              }
            },
            "description": "Sending domain removed"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no sending domain with this id belongs to the authenticated project."
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`conflict` — the domain is still in use by a template, workflow step or active campaign."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "domains:write"
            ]
          }
        ],
        "summary": "Remove a sending domain",
        "tags": [
          "Domains"
        ]
      },
      "get": {
        "description": "Fetch one sending domain by id.\n\nRequires the `domains:read` scope — View your sending domains and their verification status.",
        "operationId": "v1GetDomain",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DomainV1"
                }
              }
            },
            "description": "The sending domain"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no sending domain with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "domains:read"
            ]
          }
        ],
        "summary": "Retrieve a sending domain",
        "tags": [
          "Domains"
        ]
      }
    },
    "/api/v1/domains/{id}/verify": {
      "post": {
        "description": "Re-read this domain's state from SES and DNS and return the refreshed document.\n\nThis does NOT perform verification. Verification happens in the domain's own DNS, when its owner publishes the DKIM records SES minted at creation; Amazon decides when they resolve. What this call does is ask SES what it currently sees, re-check SPF and DMARC, and persist the answer — so a caller polling after a DNS change learns the outcome without waiting for the periodic sweep. Calling it on a domain whose records are not published yet is not an error and does not make it verify any sooner.\n\nA POST rather than a GET because it writes: the refreshed state is persisted, and a verified/unverified transition notifies the project.\n\nRequires the `domains:write` scope — Add and remove sending domains, and trigger verification.",
        "operationId": "v1VerifyDomain",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DomainV1"
                }
              }
            },
            "description": "The sending domain, as SES and DNS now report it"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no sending domain with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "domains:write"
            ]
          }
        ],
        "summary": "Refresh a sending domain's verification state",
        "tags": [
          "Domains"
        ]
      }
    },
    "/api/v1/email-validations": {
      "post": {
        "description": "Check up to 50 addresses for whether they can receive mail, and for the signals that make one worth mailing. Billed per address.\n\nThe response publishes a `verdict` alongside the flags it was drawn from. Branch on the verdict: the two obvious readings of the flags are both wrong — a free-provider address (`is_personal`) and a role mailbox (`is_role_address`) are ordinary, deliverable addresses that real customers use, and refusing them would shrink a list for no reason. `is_disposable` is the only flag that lowers a verdict.\n\n`unknown` means DNS did not answer in time, so that address was NOT checked. It is a separate value from `undeliverable` on purpose — acting on the two together deletes live contacts over a network hiccup.\n\nThe 50-address ceiling is a latency bound, not a payload one: every distinct DOMAIN in the batch costs a DNS round trip. To validate a whole list, use `POST /api/v1/lists/{id}/validation-runs`, which runs as a background job.\n\nRequires the `validation:write` scope — Check whether email addresses can receive mail — this is billed per address.",
        "operationId": "v1ValidateEmails",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EmailValidationBatchRequestV1"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailValidationBatchV1"
                }
              }
            },
            "description": "One verdict per address, in the order they were given"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "validation:write"
            ]
          }
        ],
        "summary": "Validate a batch of email addresses",
        "tags": [
          "Validation"
        ]
      }
    },
    "/api/v1/emails": {
      "post": {
        "description": "Send one transactional email and receive its delivery status in the same response. Accepts a `template` id or an inline `subject` + `body`.\n\nThis is the send to reach for when you need to know what happened. The legacy `POST /api/emails` answers with row ids and no status, so telling an accepted send from a refused one costs a second request; here the receipt carries `status`, and `from` reports the sender actually used — which is worth reading, since a template may have supplied it.\n\nExactly ONE recipient. A single receipt cannot describe a fan-out, so `to` takes one address: use `cc`/`bcc` to copy others on the same message, and `POST /api/emails/batch` to send different ones.\n\n`202 Accepted` is the success answer, and `PENDING` the usual `status`: the message is queued for the sending pipeline, not yet handed to the provider. Later states (`DELIVERED`, `BOUNCED`, …) arrive by webhook.\n\nAn optional `Idempotency-Key` header (1–255 chars, 24h TTL) makes a retry safe: the first request wins and a retry carrying the same key AND body replays its receipt. Reusing a key with a different body answers 422 rather than serving another request's result.\n\nRequires the `emails:send` scope — Send emails from your verified domains.",
        "operationId": "v1SendEmail",
        "parameters": [
          {
            "description": "Replay-safety key (24h TTL). Reuse it only to retry the identical request.",
            "in": "header",
            "name": "Idempotency-Key",
            "required": false,
            "schema": {
              "description": "Replay-safety key (24h TTL). Reuse it only to retry the identical request.",
              "maxLength": 255,
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendEmailV1"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailV1"
                }
              }
            },
            "description": "Email queued"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_disabled`, `domain_not_allowed` — the `from` address does not resolve to a verified domain on this project — or `content_rejected`: automated content review flagged the message and it was not sent."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — `template` names a template that does not belong to this project."
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`conflict` — a request with this `Idempotency-Key` is still in flight. Retry shortly."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — the body did not match the schema; or `idempotency_key_reused` — this `Idempotency-Key` was already spent on a request with a different body. Send a new key."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          },
          "503": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`content_review_unavailable` — content review could not run for this new account, so the message was not accepted. Safe to retry after a short delay."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "emails:send"
            ]
          }
        ],
        "summary": "Send a transactional email",
        "tags": [
          "Emails"
        ]
      }
    },
    "/api/v1/emails/test": {
      "post": {
        "description": "Prove that sending works — before any domain, DNS record or verification exists.\n\nThe message is sent FROM this project's sandbox address (`sandbox_address` on `GET /api/v1/projects`) and can only reach ONE recipient: the project owner's own verified account email, which is also what `to` defaults to. Naming any other recipient answers 403, and naming a `from` answers 422 — the sender is resolved server-side and a request that expects a different one is refused rather than quietly re-addressed.\n\nThat restriction is the reason `emails:test` is a separate, non-sensitive scope: a call under it cannot put mail in a stranger's inbox, so it can sit in a default grant where `emails:send` may not. It is not a way to send real mail cheaply — use `POST /api/v1/emails` for that.\n\nSandbox sends are capped per project per day, and the response's `sandbox: true` marks the receipt so a relayed summary cannot pass a test send off as a real one.\n\nRequires the `emails:test` scope — Send test emails to your own address from the Sendly sandbox.",
        "operationId": "v1SendTestEmail",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendTestEmailV1"
              }
            }
          },
          "required": true
        },
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailTestV1"
                }
              }
            },
            "description": "Test email queued"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_disabled`, or `forbidden` — the recipient is not the project owner's own verified account email (including the case where that address is not verified yet, so there is no default recipient), or `content_rejected` from automated content review."
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`conflict` — this project has no sandbox sender. The handle is derived from the project owner's email and this project has no owner; no retry fixes it."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — the body did not match the schema, most often because it named a `from`. A test send always comes from the sandbox address."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — the project's daily sandbox send cap is spent. It resets at 00:00 UTC."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          },
          "503": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`content_review_unavailable` — content review could not run for this new account. Safe to retry."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "emails:test"
            ]
          }
        ],
        "summary": "Send a sandbox test email",
        "tags": [
          "Emails"
        ]
      }
    },
    "/api/v1/events": {
      "get": {
        "description": "Cursor-paginated list of recorded events, newest first. Filter by `event_name` to follow a single series.\n\nA cursor is bound to the filters it was issued under: pairing page 2's `next_cursor` with a different `event_name` answers 422 rather than returning a page that belongs to neither query.\n\nRequires the `events:read` scope — View the custom events your application has recorded.",
        "operationId": "v1ListEvents",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "description": "Opaque cursor from a previous response's `next_cursor`.",
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "description": "Return only events with this exact name.",
            "in": "query",
            "name": "event_name",
            "required": false,
            "schema": {
              "description": "Return only events with this exact name.",
              "maxLength": 200,
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventV1List"
                }
              }
            },
            "description": "Event list"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "events:read"
            ]
          }
        ],
        "summary": "List events",
        "tags": [
          "Events"
        ]
      },
      "post": {
        "description": "Records a custom event, optionally attached to a contact. Events drive segment membership and workflow triggers, so a matching enabled workflow starts as a result of this call.\n\n`contact_id` must already exist in this project — unlike `POST /api/track`, this endpoint never creates contacts. Omit it for a project-level event.\n\nReserved system event names (`email.*`, `contact.subscribed`/`unsubscribed`, `segment.*.entry`/`.exit`) are rejected with 422: they are written by Sendly's own pipeline and accepting them from a caller would corrupt the series segments read.\n\nThis endpoint does NOT require an `Idempotency-Key`. Events are append-only and the highest-volume write on the surface; a duplicate is a data-quality question for the caller, not a money-path hazard.\n\nSending-only (`pk_*`) keys cannot record events — they hold the send capability and nothing else.\n\nRequires the `events:write` scope — Record custom events for your contacts.",
        "operationId": "v1TrackEvent",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EventTrackV1"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventV1"
                }
              }
            },
            "description": "Event recorded"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no contact with this id in the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "events:write"
            ]
          }
        ],
        "summary": "Record an event",
        "tags": [
          "Events"
        ]
      }
    },
    "/api/v1/events/names": {
      "get": {
        "description": "Every distinct event name in the project, most frequent first — the vocabulary a caller needs before filtering events or pointing a workflow trigger at one. Unpaginated: the set is bounded by what the integration emits, not by event volume.\n\nRequires the `events:read` scope — View the custom events your application has recorded.",
        "operationId": "v1ListEventNames",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventNamesV1"
                }
              }
            },
            "description": "Event names"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "events:read"
            ]
          }
        ],
        "summary": "List event names",
        "tags": [
          "Events"
        ]
      }
    },
    "/api/v1/events/stats": {
      "get": {
        "description": "Per-name event counts over a bounded window, most frequent first.\n\nThe window defaults to the last 30 days and never reaches further back than 90: this is a GROUP BY over the highest-volume table in the system, and an all-time answer is not one it can keep giving at scale. A wider request is narrowed rather than refused, and the `window` field states the range actually covered.\n\nRequires the `events:read` scope — View the custom events your application has recorded.",
        "operationId": "v1GetEventStats",
        "parameters": [
          {
            "description": "Start of the window (ISO 8601). Defaults to 30 days ago; clamped to at most 90 days back.",
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "description": "Start of the window (ISO 8601). Defaults to 30 days ago; clamped to at most 90 days back.",
              "format": "date-time",
              "type": [
                "string",
                "null"
              ]
            }
          },
          {
            "description": "End of the window (ISO 8601). Defaults to now.",
            "in": "query",
            "name": "to",
            "required": false,
            "schema": {
              "description": "End of the window (ISO 8601). Defaults to now.",
              "format": "date-time",
              "type": [
                "string",
                "null"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventStatsV1"
                }
              }
            },
            "description": "Event counts"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "events:read"
            ]
          }
        ],
        "summary": "Retrieve event counts",
        "tags": [
          "Events"
        ]
      }
    },
    "/api/v1/lists": {
      "get": {
        "description": "Cursor-paginated list of subscriber lists, newest first. Pass the previous response's `next_cursor` as `after` to page forward; `has_more` is false and `next_cursor` is null on the last page.\n\n`member_count` counts memberships in EVERY status — `PENDING` and `UNSUBSCRIBED` included — so it is the size of the membership table for this list, not the number of people currently subscribed. Read `GET /api/lists/{id}/members?status=CONFIRMED` when you want the latter.\n\nRequires the `lists:read` scope — View your subscriber lists and who is on them.",
        "operationId": "v1ListLists",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "description": "Opaque cursor from a previous response's `next_cursor`.",
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListV1List"
                }
              }
            },
            "description": "Subscriber lists"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "lists:read"
            ]
          }
        ],
        "summary": "List subscriber lists",
        "tags": [
          "Lists"
        ]
      },
      "post": {
        "description": "Create an empty subscriber list. `member_count` on the response is 0 because the list has just been created — add contacts with `POST /api/lists/{id}/subscribe`.\n\n**`double_opt_in` does not make Sendly send anything.** With it on, `POST /api/lists/{id}/subscribe` creates the membership as `PENDING` and returns a `confirm_token`; delivering `/api/lists/confirm-subscription?token=<token>` to the contact is YOUR job. A list that turns the flag on without sending that link collects pending memberships and confirms none of them.\n\n`description`, `confirmation_template_id` and `redirect_url` accept `null`, which means the same as omitting them: the field is left unset.\n\nRequires the `lists:write` scope — Create, rename, and delete your subscriber lists.",
        "operationId": "v1CreateList",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListV1Create"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListV1"
                }
              }
            },
            "description": "The created list"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "lists:write"
            ]
          }
        ],
        "summary": "Create a subscriber list",
        "tags": [
          "Lists"
        ]
      }
    },
    "/api/v1/lists/{id}": {
      "delete": {
        "description": "Delete the list and, by cascade, every membership on it. Those memberships are the consent record: an `UNSUBSCRIBED` row is the evidence that someone opted out, and it goes with the list — re-creating the list and re-importing the same addresses will not find their opt-outs waiting. The emails already sent are untouched.\n\nRequires the `lists:write` scope — Create, rename, and delete your subscriber lists.",
        "operationId": "v1DeleteList",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListV1Deleted"
                }
              }
            },
            "description": "List deleted"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no list with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "lists:write"
            ]
          }
        ],
        "summary": "Delete a subscriber list",
        "tags": [
          "Lists"
        ]
      },
      "get": {
        "description": "Fetch one list by id, with the same status-agnostic `member_count` the collection returns.\n\nRequires the `lists:read` scope — View your subscriber lists and who is on them.",
        "operationId": "v1GetList",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListV1"
                }
              }
            },
            "description": "The list"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no list with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "lists:read"
            ]
          }
        ],
        "summary": "Retrieve a subscriber list",
        "tags": [
          "Lists"
        ]
      },
      "patch": {
        "description": "Partial update. Omitted fields are left alone, and `null` for `description`, `confirmation_template_id` or `redirect_url` is treated the same as omitting them — this surface cannot clear a field back to empty yet.\n\n**`double_opt_in` does not make Sendly send anything.** With it on, `POST /api/lists/{id}/subscribe` creates the membership as `PENDING` and returns a `confirm_token`; delivering `/api/lists/confirm-subscription?token=<token>` to the contact is YOUR job. A list that turns the flag on without sending that link collects pending memberships and confirms none of them.\n\nTurning `double_opt_in` on affects only memberships created afterwards. Existing `CONFIRMED` memberships stay confirmed: those contacts consented under the rule in force when they subscribed, and demoting them would revoke a consent record rather than collect one.\n\nRequires the `lists:write` scope — Create, rename, and delete your subscriber lists.",
        "operationId": "v1UpdateList",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListV1Update"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListV1"
                }
              }
            },
            "description": "The updated list"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no list with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "lists:write"
            ]
          }
        ],
        "summary": "Update a subscriber list",
        "tags": [
          "Lists"
        ]
      }
    },
    "/api/v1/lists/{id}/validation-runs": {
      "post": {
        "description": "Start a background run over every contact on the list and answer immediately with the run. Poll `GET /api/v1/validation-runs/{id}` for progress and read the verdicts from its `/results` page.\n\nThis VALIDATES and changes nothing: no membership is unsubscribed, no contact is deleted. What to do about an `undeliverable` address is your decision, and a run that acted on its own findings would be acting on a DNS lookup that can also answer `unknown`.\n\nA second run while one is already in flight for the same list is refused with 409 rather than queued: two runs would bill the same addresses twice.\n\nRequires the `validation:write` scope — Check whether email addresses can receive mail — this is billed per address.",
        "operationId": "v1StartListValidationRun",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "202": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailValidationRunV1"
                }
              }
            },
            "description": "The run, accepted and queued"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no list with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "validation:write"
            ]
          }
        ],
        "summary": "Validate every address on a list",
        "tags": [
          "Validation"
        ]
      }
    },
    "/api/v1/projects": {
      "get": {
        "description": "The project the presented credential is scoped to — resolved from the API key, or from the `x-project-id` header for a session or delegated token. Singular despite the plural path, like `/api/v1/usage`: every v1 operation acts on exactly one project.\n\n`sandbox_address` is this project's quick-start sender. It works with no domain setup, but only to the project owner's own verified address and under a daily cap — it is how you prove sending works end to end before any DNS exists. It is null when no handle can be derived (a project with no owner).\n\nTo enumerate every project you belong to — a different question, and not project-scoped — use `GET /api/users/me/projects`.\n\nRequires the `projects:read` scope — View your projects and their settings.",
        "operationId": "v1GetProject",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectV1"
                }
              }
            },
            "description": "The authenticated project"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — the project was deleted between authentication and this read."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "projects:read"
            ]
          }
        ],
        "summary": "Retrieve the authenticated project",
        "tags": [
          "Projects"
        ]
      }
    },
    "/api/v1/segments": {
      "get": {
        "description": "Cursor-paginated list of segments, newest first. Pass the previous response's `next_cursor` as `after` to page forward; `has_more` is false and `next_cursor` is null on the last page.\n\n`member_count` is materialized on each segment, so listing segments never fans out into one count per segment — it is refreshed as membership changes rather than computed on read.\n\nRequires the `segments:read` scope — View your segments and who belongs to them.",
        "operationId": "v1ListSegments",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "description": "Opaque cursor from a previous response's `next_cursor`.",
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SegmentV1List"
                }
              }
            },
            "description": "Segment list"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "segments:read"
            ]
          }
        ],
        "summary": "List segments",
        "tags": [
          "Segments"
        ]
      },
      "post": {
        "description": "Create a `DYNAMIC` segment (a saved `condition`, re-evaluated against contacts on every read) or a `STATIC` one (an explicitly managed membership list). `type` is fixed at creation — it decides how membership is computed, so it cannot be changed later.\n\nA `DYNAMIC` segment requires a `condition`, which is validated and evaluated during the request: the response's `member_count` tells you immediately how many contacts the filter actually matches.\n\nRequires the `segments:write` scope — Create, edit, and delete your segments.",
        "operationId": "v1CreateSegment",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SegmentV1Create"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SegmentV1"
                }
              }
            },
            "description": "Segment created"
          },
          "400": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — a `DYNAMIC` segment was submitted without a `condition`."
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "segments:write"
            ]
          }
        ],
        "summary": "Create a segment",
        "tags": [
          "Segments"
        ]
      }
    },
    "/api/v1/segments/{id}": {
      "delete": {
        "description": "Delete a segment. Refused with 409 while any `DRAFT`, `SCHEDULED`, or `SENDING` campaign still targets it — deleting it would leave those campaigns pointing at an audience that no longer exists, and the failure would surface at send time instead of here. Remove the segment from those campaigns first.\n\nRequires the `segments:write` scope — Create, edit, and delete your segments.",
        "operationId": "v1DeleteSegment",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SegmentV1Deleted"
                }
              }
            },
            "description": "Segment deleted"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no segment with this id belongs to the authenticated project."
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`conflict` — the segment is still used by one or more active campaigns."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "segments:write"
            ]
          }
        ],
        "summary": "Delete a segment",
        "tags": [
          "Segments"
        ]
      },
      "get": {
        "description": "Fetch one segment, including its saved `condition` and materialized `member_count`.\n\nRequires the `segments:read` scope — View your segments and who belongs to them.",
        "operationId": "v1GetSegment",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SegmentV1"
                }
              }
            },
            "description": "The segment"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no segment with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "segments:read"
            ]
          }
        ],
        "summary": "Retrieve a segment",
        "tags": [
          "Segments"
        ]
      },
      "patch": {
        "description": "Partial update: an omitted field is left untouched. Changing a `DYNAMIC` segment's `condition` recomputes `member_count` in the same call, so the returned object never states a size that belongs to the previous filter. `condition` is ignored on a `STATIC` segment, whose membership is the explicit list.\n\n`type` is not accepted here — see the create operation.\n\nRequires the `segments:write` scope — Create, edit, and delete your segments.",
        "operationId": "v1UpdateSegment",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SegmentV1Update"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SegmentV1"
                }
              }
            },
            "description": "The updated segment"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no segment with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "segments:write"
            ]
          }
        ],
        "summary": "Update a segment",
        "tags": [
          "Segments"
        ]
      }
    },
    "/api/v1/segments/{id}/contacts": {
      "get": {
        "description": "Cursor-paginated members of a segment. For a `STATIC` segment these are the rows of its membership list; for a `DYNAMIC` one the saved `condition` is evaluated against contacts as the page is read, so the result always reflects the contacts as they are now.\n\nCursors from this endpoint are not interchangeable with cursors from other list endpoints — the ordering differs — and pairing one with the wrong endpoint answers 422 rather than silently paging a different set.\n\nRequires the `segments:read` scope — View your segments and who belongs to them.",
        "operationId": "v1ListSegmentContacts",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "description": "Opaque cursor from a previous response's `next_cursor`.",
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SegmentContactV1List"
                }
              }
            },
            "description": "Segment member list"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no segment with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "segments:read"
            ]
          }
        ],
        "summary": "List the contacts in a segment",
        "tags": [
          "Segments"
        ]
      }
    },
    "/api/v1/suppressions": {
      "get": {
        "description": "Cursor-paginated list of suppressed addresses, newest first. Pass the previous response's `next_cursor` as `after` to page forward; `has_more` is false and `next_cursor` is null on the last page.\n\nA cursor is bound to the filters that minted it. Changing `reason` while reusing a cursor answers `422 validation_error` rather than returning a page that belongs to neither query — drop the cursor and start again from the first page.\n\nRequires the `suppression:read` scope — View the addresses on your suppression list.",
        "operationId": "v1ListSuppressions",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "description": "Opaque cursor from a previous response's `next_cursor`.",
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "description": "Filter to one reason. Omit for every suppressed address.",
            "in": "query",
            "name": "reason",
            "required": false,
            "schema": {
              "description": "Filter to one reason. Omit for every suppressed address.",
              "enum": [
                "HARD_BOUNCE",
                "COMPLAINT",
                "MANUAL",
                "UNSUBSCRIBE"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuppressionV1List"
                }
              }
            },
            "description": "Suppression list"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "suppression:read"
            ]
          }
        ],
        "summary": "List suppressed addresses",
        "tags": [
          "Suppression"
        ]
      },
      "post": {
        "description": "Add an address to this project's suppression list, so no further send reaches it.\n\nIdempotent: suppressing an already-suppressed address answers `201` with the EXISTING record rather than `409`. The first `reason` and `source` win, because a later manual entry must not overwrite what an SES bounce recorded.\n\n`source` is NOT accepted in the body — it is derived from the credential (`API` for an API key, `DASHBOARD` for a session), so a record's provenance cannot be dressed up as a deliverability fact.\n\nRequires the `suppression:write` scope — Add and remove addresses on your suppression list.",
        "operationId": "v1CreateSuppression",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SuppressionV1Create"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuppressionV1"
                }
              }
            },
            "description": "The suppressed address"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "suppression:write"
            ]
          }
        ],
        "summary": "Suppress an address",
        "tags": [
          "Suppression"
        ]
      }
    },
    "/api/v1/suppressions/{email}": {
      "delete": {
        "description": "Clear Sendly's suppression record for this address, so the send pipeline stops refusing it. This is the one operation on this surface that can put mail back into an inbox that asked you to stop, which is why `suppression:write` is a sensitive scope.\n\nIt does NOT remove the address from AWS SES's own account-level suppression list. SES maintains that list independently of anything Sendly stores, so an address SES suppressed after a hard bounce stays undeliverable through SES even once this record is gone — removing it here is not a promise that the next send arrives.\n\nIdempotent: an address that was never suppressed answers `200` too, because \"not on the list\" is the state you asked for.\n\nRequires the `suppression:write` scope — Add and remove addresses on your suppression list.",
        "operationId": "v1DeleteSuppression",
        "parameters": [
          {
            "description": "The suppressed address, URL-encoded.",
            "in": "path",
            "name": "email",
            "required": true,
            "schema": {
              "description": "The suppressed address, URL-encoded.",
              "format": "email",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuppressionV1Deleted"
                }
              }
            },
            "description": "Address removed from the suppression list"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "suppression:write"
            ]
          }
        ],
        "summary": "Remove an address from the suppression list",
        "tags": [
          "Suppression"
        ]
      },
      "get": {
        "description": "Fetch the suppression record for one address. The path parameter is the address itself, URL-encoded.\n\nAn address that is NOT suppressed answers `404 resource_not_found` — this path addresses the suppression record, and there is none. The answer is definite either way: `200` means suppressed and says why, `404` means not suppressed.\n\nA `200` may also come from a platform-wide block recorded outside this project, in which case the address is genuinely undeliverable for you even though you never suppressed it.\n\nRequires the `suppression:read` scope — View the addresses on your suppression list.",
        "operationId": "v1GetSuppression",
        "parameters": [
          {
            "description": "The suppressed address, URL-encoded.",
            "in": "path",
            "name": "email",
            "required": true,
            "schema": {
              "description": "The suppressed address, URL-encoded.",
              "format": "email",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuppressionV1"
                }
              }
            },
            "description": "The suppression record"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — this address is not suppressed for the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "suppression:read"
            ]
          }
        ],
        "summary": "Check whether an address is suppressed",
        "tags": [
          "Suppression"
        ]
      }
    },
    "/api/v1/templates": {
      "get": {
        "description": "Cursor-paginated list of templates, newest first. Pass the previous response's `next_cursor` as `after` to page forward; `has_more` is false and `next_cursor` is null on the last page.\n\n`search` is a case-insensitive substring match on the NAME only — narrower than the dashboard's search, which also reads description and subject, because a caller that asked for a name match should not be handed rows that merely mention the word in their body.\n\nA cursor is bound to the filters that minted it. Changing `search` or `email_category` while reusing a cursor answers `422 validation_error` rather than returning a page that belongs to neither query — drop the cursor and start again from the first page.\n\nRequires the `templates:read` scope — View your email templates.",
        "operationId": "v1ListTemplates",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "description": "Opaque cursor from a previous response's `next_cursor`.",
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "description": "Case-insensitive substring match on the name.",
            "in": "query",
            "name": "search",
            "required": false,
            "schema": {
              "description": "Case-insensitive substring match on the name.",
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "email_category",
            "required": false,
            "schema": {
              "enum": [
                "TRANSACTIONAL",
                "MARKETING",
                "SELF_MANAGED_UNSUBSCRIBE"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateV1List"
                }
              }
            },
            "description": "Template list"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "templates:read"
            ]
          }
        ],
        "summary": "List templates",
        "tags": [
          "Templates"
        ]
      },
      "post": {
        "description": "Create a template. The `from` domain must already be a verified sending identity for this project — an unverified sender answers `403 forbidden` here rather than becoming a campaign that fails at send time.\n\nRequires the `templates:write` scope — Create, edit, and delete your email templates.",
        "operationId": "v1CreateTemplate",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TemplateV1Create"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateV1"
                }
              }
            },
            "description": "The created template"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          }
        ],
        "summary": "Create a template",
        "tags": [
          "Templates"
        ]
      }
    },
    "/api/v1/templates/{id}": {
      "delete": {
        "description": "Delete the template. Answers `409 conflict` while a workflow step or an active campaign (DRAFT, SCHEDULED or SENDING) still points at it — removing it would leave those referring to content that no longer exists, and the failure would surface at send time instead of here. The emails already sent from this template are NOT erased.\n\nRequires the `templates:write` scope — Create, edit, and delete your email templates.",
        "operationId": "v1DeleteTemplate",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateV1Deleted"
                }
              }
            },
            "description": "Template deleted"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no template with this id belongs to the authenticated project."
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`conflict` — the template is still referenced by a workflow step or an active campaign."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          }
        ],
        "summary": "Delete a template",
        "tags": [
          "Templates"
        ]
      },
      "get": {
        "description": "Fetch one template by id.\n\nRequires the `templates:read` scope — View your email templates.",
        "operationId": "v1GetTemplate",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateV1"
                }
              }
            },
            "description": "The template"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no template with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "templates:read"
            ]
          }
        ],
        "summary": "Retrieve a template",
        "tags": [
          "Templates"
        ]
      },
      "patch": {
        "description": "Partial update. Omitted fields are left alone.\n\nChanging `subject`, `body`, `from`, `from_name` or `reply_to` snapshots the previous content into the template's version history and increments `version`; changing only `name`, `description` or `email_category` does not, because neither is content a send would have rendered.\n\nA `from` supplied here is verified before anything is written, on the same terms as create.\n\nRequires the `templates:write` scope — Create, edit, and delete your email templates.",
        "operationId": "v1UpdateTemplate",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TemplateV1Update"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TemplateV1"
                }
              }
            },
            "description": "The updated template"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no template with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "templates:write"
            ]
          }
        ],
        "summary": "Update a template",
        "tags": [
          "Templates"
        ]
      }
    },
    "/api/v1/topics": {
      "get": {
        "description": "The subjects this project mails about, cursor-paginated and newest first.\n\nArchived topics are omitted unless you ask for them with `include_archived`. Archiving is the retire button and there is no delete: a topic is where people's answers are recorded, so removing it would remove the choices they made.\n\n`subscribed_count` counts contacts who explicitly said yes. It reads low on a topic with `default_opt_in` true, and that is the honest number — it is how many people answered, not how many would currently receive the mail.\n\nRequires the `topics:read` scope — View the topics you mail about and who is subscribed to each.",
        "operationId": "v1ListTopics",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "description": "Opaque cursor from a previous response's `next_cursor`.",
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "include_archived",
            "required": false,
            "schema": {
              "type": [
                "boolean",
                "null"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TopicListV1"
                }
              }
            },
            "description": "One page of topics"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "topics:read"
            ]
          }
        ],
        "summary": "List topics",
        "tags": [
          "Topics"
        ]
      },
      "post": {
        "description": "`key` is the stable, project-unique name every preference form and integration refers to, so it survives a rename of `name` and cannot be edited afterwards.\n\n`default_opt_in` is the field worth thinking about: it decides what SILENCE means for every contact who never answers. Leave it true for a topic introduced over a list you already have — those contacts consented to hear from you, and inventing an opt-out they never asked for would mute mail they expect. Set it false for anything a person has to ask for, and absence then means `not asked` rather than `no`.\n\nRequires the `topics:write` scope — Create and edit topics, and change what your contacts are subscribed to — this decides who your campaigns reach.",
        "operationId": "v1CreateTopic",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TopicCreateV1"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TopicV1"
                }
              }
            },
            "description": "The created topic"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "topics:write"
            ]
          }
        ],
        "summary": "Create a topic",
        "tags": [
          "Topics"
        ]
      }
    },
    "/api/v1/topics/{id}": {
      "get": {
        "description": "Requires the `topics:read` scope — View the topics you mail about and who is subscribed to each.",
        "operationId": "v1GetTopic",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TopicV1"
                }
              }
            },
            "description": "The topic"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no topic with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "topics:read"
            ]
          }
        ],
        "summary": "Retrieve a topic",
        "tags": [
          "Topics"
        ]
      },
      "patch": {
        "description": "Rename it, re-describe it, flip `default_opt_in`, or archive it.\n\n`key` is absent from the body on purpose. Every stored preference and every integration refers to a topic by its key, so changing one would orphan them silently. There is no DELETE for the same reason — `archived: true` removes the topic from the preference centre and from new sends while every opt-out recorded against it survives.\n\nRequires the `topics:write` scope — Create and edit topics, and change what your contacts are subscribed to — this decides who your campaigns reach.",
        "operationId": "v1UpdateTopic",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TopicUpdateV1"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TopicV1"
                }
              }
            },
            "description": "The updated topic"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no topic with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "topics:write"
            ]
          }
        ],
        "summary": "Update a topic",
        "tags": [
          "Topics"
        ]
      }
    },
    "/api/v1/topics/{id}/subscriptions": {
      "post": {
        "description": "The two directions behave differently, and the asymmetry is deliberate: consent needs proof, withdrawal of consent does not.\n\n`subscribed: true` does NOT subscribe anyone. It parks the contact at `pending` and returns a `confirmation_url`; nothing is mailed on this topic until somebody opens it. There is no parameter to skip that step. A caller asserting a subscription is not evidence that the mailbox holder agreed — anyone can type an address into a form — and treating the assertion as consent is the exact failure double opt-in exists to prevent. Sendly does not send the confirmation email; you do, from your own verified domain, because it is your relationship with the contact and your sending reputation.\n\n`subscribed: false` records the opt-out immediately. Requiring somebody to confirm that they want to stop is a dark pattern, and it is also the fastest route to a spam report.\n\nRequires the `topics:write` scope — Create and edit topics, and change what your contacts are subscribed to — this decides who your campaigns reach.",
        "operationId": "v1SetTopicSubscription",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TopicSubscribeV1"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TopicSubscriptionV1"
                }
              }
            },
            "description": "The resulting subscription"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no topic with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "topics:write"
            ]
          }
        ],
        "summary": "Subscribe or unsubscribe a contact from a topic",
        "tags": [
          "Topics"
        ]
      }
    },
    "/api/v1/usage": {
      "get": {
        "description": "Email usage against the limits that are actually enforced: the current month's counts per source category, the monthly cap applied to their total, and today's sends against the trust-tier daily ceiling.\n\nEvery figure is read from an enforcement path, so what this reports and what refuses a send cannot disagree. Correspondingly, nothing else is published — there is no billing period, invoice total or non-email meter here, because the platform meters none of those.\n\nTwo caveats worth reading before you alert on these numbers:\n\n- The windows differ. The monthly counters roll over on the SERVER's calendar month; the daily counter buckets on the UTC date. The two therefore reset at different instants.\n- `monthly.limit` is null once a subscription makes sending metered rather than capped, and also when an operator has set per-category limits — in that case the caps live in `monthly.categories[*].limit`.\n\nRequires the `usage:read` scope — View your usage totals and billing limits.",
        "operationId": "v1GetUsage",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UsageV1"
                }
              }
            },
            "description": "Current usage"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "usage:read"
            ]
          }
        ],
        "summary": "Retrieve current usage and limits",
        "tags": [
          "Usage"
        ]
      }
    },
    "/api/v1/validation-runs/{id}": {
      "get": {
        "description": "How far a run has got and what it found. The counters are advanced by each batch in the same step that commits the rows they count, so they are a ledger rather than a cached estimate and may be branched on.\n\nThere is no total to divide by, deliberately: a list changes size while a run walks it, so a denominator captured when the run started would be wrong by the time you read it. A run is finished when `status` is `completed` or `failed`, never when a percentage reaches 100.\n\nRequires the `validation:read` scope — View your email validation runs and their results.",
        "operationId": "v1GetValidationRun",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailValidationRunV1"
                }
              }
            },
            "description": "The run"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no validation run with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "validation:read"
            ]
          }
        ],
        "summary": "Retrieve a validation run",
        "tags": [
          "Validation"
        ]
      }
    },
    "/api/v1/validation-runs/{id}/results": {
      "get": {
        "description": "One page of a run's verdicts, cursor-paginated. Filter with `verdict` — `undeliverable` is the page you want when you are about to act on the results.\n\nNo total is reported: a run over a large list holds millions of rows, and counting them on every page read is the query this design exists to avoid. The run's own counters carry the numbers worth having.\n\n`contact_id` is the contact the address belonged to when it was checked, and it is null for a contact deleted since. The `email` is stored on the result rather than read through the contact, so a completed run reports what it actually checked even after the contact changed address.\n\nRequires the `validation:read` scope — View your email validation runs and their results.",
        "operationId": "v1ListValidationRunResults",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 200,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "description": "Opaque cursor from a previous response's `next_cursor`.",
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "description": "Return only results with this verdict — `undeliverable` is the usual filter.",
            "in": "query",
            "name": "verdict",
            "required": false,
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/EmailValidationVerdictV1"
                },
                {
                  "description": "Return only results with this verdict — `undeliverable` is the usual filter."
                }
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EmailValidationResultListV1"
                }
              }
            },
            "description": "One page of results"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no validation run with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "validation:read"
            ]
          }
        ],
        "summary": "List a validation run's results",
        "tags": [
          "Validation"
        ]
      }
    },
    "/api/v1/webhooks": {
      "get": {
        "description": "Cursor-paginated list of webhook endpoints, newest first. Pass the previous response's `next_cursor` as `after` to page forward; `has_more` is false and `next_cursor` is null on the last page.\n\nSigning secrets are not on this response and cannot be read back — see `POST /api/v1/webhooks/{id}/rotate-secret` if you have lost one.\n\nRequires the `webhooks:read` scope — View your webhook endpoints and their delivery history.",
        "operationId": "v1ListWebhooks",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "description": "Opaque cursor from a previous response's `next_cursor`.",
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookV1List"
                }
              }
            },
            "description": "Webhook list"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "webhooks:read"
            ]
          }
        ],
        "summary": "List webhooks",
        "tags": [
          "Webhooks"
        ]
      },
      "post": {
        "description": "Register an endpoint to receive HMAC-signed deliveries for the events named in `event_types`.\n\nThe response carries the signing secret ONCE. It is shown here and by `POST /api/v1/webhooks/{id}/rotate-secret`, and by nothing else — no endpoint reads it back, so store it now. A secret you have lost is replaced by rotating, not recovered.\n\nRequires the `webhooks:write` scope — Create, edit, and delete your webhook endpoints.",
        "operationId": "v1CreateWebhook",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookV1Create"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookV1Created"
                }
              }
            },
            "description": "The created webhook and its one-time signing secret"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          }
        ],
        "summary": "Create a webhook",
        "tags": [
          "Webhooks"
        ]
      }
    },
    "/api/v1/webhooks/{id}": {
      "delete": {
        "description": "Remove the endpoint and its delivery history — a delivery attempt is a fact about this endpoint and has no meaning once the endpoint is gone. Deliveries already in flight are not recalled, so the endpoint may still receive an event shortly after this returns.\n\nRequires the `webhooks:write` scope — Create, edit, and delete your webhook endpoints.",
        "operationId": "v1DeleteWebhook",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookV1Deleted"
                }
              }
            },
            "description": "Webhook deleted"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no webhook with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          }
        ],
        "summary": "Delete a webhook",
        "tags": [
          "Webhooks"
        ]
      },
      "get": {
        "description": "Fetch one webhook endpoint by id. The signing secret is not part of this response.\n\nRequires the `webhooks:read` scope — View your webhook endpoints and their delivery history.",
        "operationId": "v1GetWebhook",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookV1"
                }
              }
            },
            "description": "The webhook"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no webhook with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "webhooks:read"
            ]
          }
        ],
        "summary": "Retrieve a webhook",
        "tags": [
          "Webhooks"
        ]
      },
      "patch": {
        "description": "Partial update. Omitted fields are left alone.\n\n`event_types` REPLACES the stored subscription list rather than merging into it, so an event you omit is unsubscribed. Setting `status` back to `ACTIVE` from `DISABLED` also clears the consecutive-failure counter, so an auto-disabled endpoint gets a clean slate.\n\nThe signing secret is untouched by an update, and is not part of this response.\n\nRequires the `webhooks:write` scope — Create, edit, and delete your webhook endpoints.",
        "operationId": "v1UpdateWebhook",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WebhookV1Update"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookV1"
                }
              }
            },
            "description": "The updated webhook"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no webhook with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          }
        ],
        "summary": "Update a webhook",
        "tags": [
          "Webhooks"
        ]
      }
    },
    "/api/v1/webhooks/{id}/rotate-secret": {
      "post": {
        "description": "Mint a fresh signing secret. The new plaintext is returned EXACTLY ONCE, here — no endpoint reads it back, so a secret you lose is replaced by rotating again rather than recovered.\n\nRotation overlaps on purpose. The outgoing secret keeps verifying until `previous_secret_expires_at`, and every delivery inside that window carries BOTH signatures in the `webhook-signature` header — so you can deploy the new secret to your verifier whenever you like without dropping an in-flight event. Rotating twice inside the window discards the older secret: only one previous secret is ever live.\n\n`url`, `event_types` and `status` are unchanged.\n\nRequires the `webhooks:write` scope — Create, edit, and delete your webhook endpoints.",
        "operationId": "v1RotateWebhookSecret",
        "parameters": [
          {
            "description": "Resource id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Resource id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookV1SecretRotated"
                }
              }
            },
            "description": "The new signing secret and the moment the previous one stops verifying"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no webhook with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          }
        ],
        "summary": "Rotate a webhook signing secret",
        "tags": [
          "Webhooks"
        ]
      }
    },
    "/api/v1/workflows": {
      "get": {
        "description": "Cursor-paginated list of workflows, newest first. Pass the previous response's `next_cursor` as `after` to page forward; `has_more` is false and `next_cursor` is null on the last page.\n\nUnlike the legacy `/api/*` endpoints, v1 responses are the bare payload (no `{ success, data }` envelope) and errors are RFC 9457 problem documents.\n\nRequires the `workflows:read` scope — View your automation workflows and their runs.",
        "operationId": "v1ListWorkflows",
        "parameters": [
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "description": "Opaque cursor from a previous response's `next_cursor`.",
              "minLength": 1,
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowV1List"
                }
              }
            },
            "description": "Workflow list"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "workflows:read"
            ]
          }
        ],
        "summary": "List workflows",
        "tags": [
          "Workflows"
        ]
      },
      "post": {
        "description": "Creates a workflow with its single trigger step. `trigger_type` defaults to `EVENT`, which requires `event_name`; `SCHEDULE` takes `interval_ms` and `MANUAL` is started only by `POST /api/v1/workflows/{id}/executions`.\n\nPass `sequence` to create the steps at the same time, as a LINEAR chain behind the trigger. Anything with a branch is `PUT /api/v1/workflows/{id}/graph`. Without a sequence the workflow holds only its trigger and stays inert until it has steps to run, which is why it is created disabled.\n\nRequires the `workflows:write` scope — Create, edit, enable, and delete your automation workflows.",
        "operationId": "v1CreateWorkflow",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkflowCreateV1"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowV1"
                }
              }
            },
            "description": "Workflow created"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "workflows:write"
            ]
          }
        ],
        "summary": "Create a workflow",
        "tags": [
          "Workflows"
        ]
      }
    },
    "/api/v1/workflows/executions/{execution_id}/cancel": {
      "post": {
        "description": "Stops one run and stamps it `CANCELLED`. The execution stays queryable — cancelling is a state change, not a delete. Addressed by execution id alone, so a caller holding one from a list does not need to carry the workflow id with it.\n\nRequires the `workflows:write` scope — Create, edit, enable, and delete your automation workflows.",
        "operationId": "v1CancelWorkflowExecution",
        "parameters": [
          {
            "description": "Workflow execution id.",
            "in": "path",
            "name": "execution_id",
            "required": true,
            "schema": {
              "description": "Workflow execution id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowExecutionV1"
                }
              }
            },
            "description": "Cancelled execution"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no execution with this id in the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "workflows:write"
            ]
          }
        ],
        "summary": "Cancel a workflow execution",
        "tags": [
          "Workflows"
        ]
      }
    },
    "/api/v1/workflows/{id}": {
      "delete": {
        "description": "Refused with 409 while executions are still running: deleting a workflow cascades its executions away, and a contact mid-journey disappearing is data loss the caller cannot detect afterwards. Disable the workflow or cancel its runs first.\n\nRequires the `workflows:write` scope — Create, edit, enable, and delete your automation workflows.",
        "operationId": "v1DeleteWorkflow",
        "parameters": [
          {
            "description": "Workflow id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Workflow id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowDeletedV1"
                }
              }
            },
            "description": "Workflow deleted"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no workflow with this id belongs to the authenticated project."
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`conflict` — the workflow still has running executions."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "workflows:write"
            ]
          }
        ],
        "summary": "Delete a workflow",
        "tags": [
          "Workflows"
        ]
      },
      "get": {
        "description": "The workflow itself — its trigger, re-entry policy and rate cap. The step graph is not part of the v1 contract.\n\nRequires the `workflows:read` scope — View your automation workflows and their runs.",
        "operationId": "v1GetWorkflow",
        "parameters": [
          {
            "description": "Workflow id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Workflow id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowV1"
                }
              }
            },
            "description": "Workflow"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no workflow with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "workflows:read"
            ]
          }
        ],
        "summary": "Retrieve a workflow",
        "tags": [
          "Workflows"
        ]
      },
      "patch": {
        "description": "Sparse update — omitted fields are left unchanged.\n\nTwo state rules apply: the trigger (`trigger_type`/`event_name`/`interval_ms`) cannot be changed while the workflow has running executions (409), and `enabled: true` is refused while any step is still unconfigured (422), because an enabled workflow accepts contacts immediately and would otherwise fail only once one reached the broken step.\n\n`sequence` REPLACES every non-trigger step with a linear chain and is likewise refused while executions are running. Omit it to leave the graph untouched.\n\nRequires the `workflows:write` scope — Create, edit, enable, and delete your automation workflows.",
        "operationId": "v1UpdateWorkflow",
        "parameters": [
          {
            "description": "Workflow id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Workflow id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkflowUpdateV1"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowV1"
                }
              }
            },
            "description": "Updated workflow"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no workflow with this id belongs to the authenticated project."
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`conflict` — the trigger cannot be changed while executions are running."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "workflows:write"
            ]
          }
        ],
        "summary": "Update a workflow",
        "tags": [
          "Workflows"
        ]
      }
    },
    "/api/v1/workflows/{id}/clone": {
      "post": {
        "description": "Copies a workflow and its whole graph as a new workflow. The copy is always created disabled, whatever the original was: a clone exists to be reviewed, and one that started live would match the same trigger events as its original from the moment it appeared.\n\nServer-side rather than a read-then-write, so the copy is taken from one consistent read of the source.\n\nRequires the `workflows:write` scope — Create, edit, enable, and delete your automation workflows.",
        "operationId": "v1CloneWorkflow",
        "parameters": [
          {
            "description": "Workflow id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Workflow id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkflowCloneV1"
              }
            }
          },
          "required": false
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowV1"
                }
              }
            },
            "description": "The cloned workflow"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no workflow with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "workflows:write"
            ]
          }
        ],
        "summary": "Clone a workflow",
        "tags": [
          "Workflows"
        ]
      }
    },
    "/api/v1/workflows/{id}/executions": {
      "get": {
        "description": "One row per contact-run, newest first, cursor-paginated on the execution's start time. Filter by `status` to find stuck (`WAITING`) or failed runs.\n\nRequires the `workflows:read` scope — View your automation workflows and their runs.",
        "operationId": "v1ListWorkflowExecutions",
        "parameters": [
          {
            "description": "Workflow id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Workflow id.",
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 20,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "description": "Opaque cursor from a previous response's `next_cursor`.",
            "in": "query",
            "name": "after",
            "required": false,
            "schema": {
              "description": "Opaque cursor from a previous response's `next_cursor`.",
              "minLength": 1,
              "type": "string"
            }
          },
          {
            "description": "Return only executions in this state.",
            "in": "query",
            "name": "status",
            "required": false,
            "schema": {
              "description": "Return only executions in this state.",
              "enum": [
                "RUNNING",
                "WAITING",
                "COMPLETED",
                "EXITED",
                "FAILED",
                "CANCELLED"
              ],
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowExecutionV1List"
                }
              }
            },
            "description": "Execution list"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no workflow with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "workflows:read"
            ]
          }
        ],
        "summary": "List a workflow's executions",
        "tags": [
          "Workflows"
        ]
      },
      "post": {
        "description": "Enters one contact into an enabled workflow. Step processing runs asynchronously, so a 201 means the run was claimed — not that it finished.\n\n409 when the workflow's re-entry policy already accounts for this contact; 429 when the workflow's own `max_executions_per_hour` cap is reached.\n\nRequires the `workflows:write` scope — Create, edit, enable, and delete your automation workflows.",
        "operationId": "v1StartWorkflowExecution",
        "parameters": [
          {
            "description": "Workflow id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Workflow id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkflowExecutionStartV1"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowExecutionV1"
                }
              }
            },
            "description": "Execution started"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no such workflow, or no such contact in this project."
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`conflict` — the contact already has an execution and re-entry is not allowed."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "workflows:write"
            ]
          }
        ],
        "summary": "Start a workflow for a contact",
        "tags": [
          "Workflows"
        ]
      }
    },
    "/api/v1/workflows/{id}/graph": {
      "get": {
        "description": "Every step in the workflow, including its `TRIGGER` entry node, and every directed transition between them. `version` is the workflow's version at the time of the read — a different number on a later read means somebody edited the graph in between.\n\nA step's `config` is returned exactly as stored, camelCase keys and all, rather than projected into the snake_case used everywhere else on this API. That is deliberate: the same document is authored by the visual editor, and renaming its keys on the way out would mean any key this API did not know about was silently dropped on the way back in.\n\nThe response of `GET /api/v1/workflows/{id}/graph` is accepted verbatim by `PUT` on the same path: read a graph, change a step, send it back. Step and transition ids are yours to choose on a write, which is what makes that round trip a no-op rather than a rebuild.\n\nRequires the `workflows:read` scope — View your automation workflows and their runs.",
        "operationId": "v1GetWorkflowGraph",
        "parameters": [
          {
            "description": "Workflow id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Workflow id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowGraphV1"
                }
              }
            },
            "description": "The workflow's graph"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no workflow with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "workflows:read"
            ]
          }
        ],
        "summary": "Retrieve a workflow's step graph",
        "tags": [
          "Workflows"
        ]
      },
      "put": {
        "description": "Replaces the whole graph in one transaction, because a graph is only meaningful as nodes PLUS the edges between them — an edit that could apply half of it would leave steps pointing at steps that no longer exist.\n\nA step whose id you send is kept and updated in place; a fresh uuid creates one; an id you omit deletes that step and its run history. Exactly one step must be a `TRIGGER`, every transition must name steps present in the same document, and a step may not point at itself.\n\nRefused with 409 while the workflow has running executions: those runs are standing on the steps being replaced. Pause the workflow (`POST /api/v1/workflows/{id}/pause`) first.\n\nThe response of `GET /api/v1/workflows/{id}/graph` is accepted verbatim by `PUT` on the same path: read a graph, change a step, send it back. Step and transition ids are yours to choose on a write, which is what makes that round trip a no-op rather than a rebuild.\n\nRequires the `workflows:write` scope — Create, edit, enable, and delete your automation workflows.",
        "operationId": "v1ReplaceWorkflowGraph",
        "parameters": [
          {
            "description": "Workflow id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Workflow id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/WorkflowGraphReplaceV1"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowGraphV1"
                }
              }
            },
            "description": "The graph as it now stands"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no workflow with this id belongs to the authenticated project."
          },
          "409": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`conflict` — the workflow has running executions, or an id in the document already belongs to a different workflow."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "workflows:write"
            ]
          }
        ],
        "summary": "Replace a workflow's step graph",
        "tags": [
          "Workflows"
        ]
      }
    },
    "/api/v1/workflows/{id}/pause": {
      "post": {
        "description": "Disables the workflow and cancels every `RUNNING`/`WAITING` execution inside it.\n\n`PATCH { \"enabled\": false }` stops NEW runs starting and leaves every in-flight contact walking the graph — the next delay still expires, the next email still sends. Pausing does both, and reports how many runs it stopped.\n\nCancelling is terminal: `resume` re-opens the workflow to new runs, it does not put the cancelled contacts back where they were.\n\nRequires the `workflows:write` scope — Create, edit, enable, and delete your automation workflows.",
        "operationId": "v1PauseWorkflow",
        "parameters": [
          {
            "description": "Workflow id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Workflow id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowStateChangeV1"
                }
              }
            },
            "description": "The workflow, and the number of runs this call cancelled"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no workflow with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "workflows:write"
            ]
          }
        ],
        "summary": "Pause a workflow and cancel its running executions",
        "tags": [
          "Workflows"
        ]
      }
    },
    "/api/v1/workflows/{id}/resume": {
      "post": {
        "description": "Re-enables the workflow so its trigger matches again. `cancelled_executions` is always 0 here — resuming starts nothing and stops nothing.\n\nRefused with 422 while any step is still unconfigured, the same rule `PATCH { \"enabled\": true }` enforces: an enabled workflow accepts contacts immediately and would otherwise fail only once one reached the broken step.\n\nRequires the `workflows:write` scope — Create, edit, enable, and delete your automation workflows.",
        "operationId": "v1ResumeWorkflow",
        "parameters": [
          {
            "description": "Workflow id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Workflow id.",
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowStateChangeV1"
                }
              }
            },
            "description": "The workflow, with `cancelled_executions` always 0"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no workflow with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "workflows:write"
            ]
          }
        ],
        "summary": "Resume a paused workflow",
        "tags": [
          "Workflows"
        ]
      }
    },
    "/api/v1/workflows/{id}/stats": {
      "get": {
        "description": "Execution counts by status, average completion time, the emails this workflow sent (with opens and clicks), and per-goal conversion counts. All-time by default — pass `from` to narrow it. Unlike `/api/v1/analytics/*` there is no 90-day ceiling here, because every aggregate is already confined to this one workflow.\n\nThe workflow's own `name`, `enabled`, `trigger_type` and `step_count` travel with the counts, because the counts alone are ambiguous: no running executions means one thing on an enabled workflow and another on a paused one.\n\nRequires the `workflows:read` scope — View your automation workflows and their runs.",
        "operationId": "v1GetWorkflowStats",
        "parameters": [
          {
            "description": "Workflow id.",
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "description": "Workflow id.",
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "description": "Only count executions started at or after this instant (ISO 8601). Defaults to all time.",
            "in": "query",
            "name": "from",
            "required": false,
            "schema": {
              "description": "Only count executions started at or after this instant (ISO 8601). Defaults to all time.",
              "format": "date-time",
              "type": [
                "string",
                "null"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WorkflowStatsV1"
                }
              }
            },
            "description": "Workflow statistics"
          },
          "401": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`invalid_api_key` or `invalid_session` — missing or invalid credentials."
          },
          "403": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`scope_missing`, `project_access_denied`, or `project_disabled`."
          },
          "404": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`resource_not_found` — no workflow with this id belongs to the authenticated project."
          },
          "422": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`validation_error` — query, path, or body parameters did not match the schema."
          },
          "429": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`rate_limited` — see `Retry-After` and the `RateLimit` headers."
          },
          "500": {
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            },
            "description": "`internal_error`."
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "workflows:read"
            ]
          }
        ],
        "summary": "Retrieve workflow statistics",
        "tags": [
          "Workflows"
        ]
      }
    },
    "/api/verify": {
      "post": {
        "description": "Open endpoint (no auth required) that checks an email for syntax, MX records, disposable domains, and plus-addressing. Used by the marketing site verifier.",
        "operationId": "verifyEmailAddress",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyEmail"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VerifyEmailResponse"
                }
              }
            },
            "description": "Verification result"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [],
        "summary": "Validate an email address",
        "tags": [
          "Verify"
        ]
      }
    },
    "/api/webhooks": {
      "get": {
        "description": "List all user-managed outbound webhooks for the auth'd project (secrets are not returned).\n\nRequires the `webhooks:read` scope — View your webhook endpoints and their delivery history.",
        "operationId": "listWebhooks",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookListResponse"
                }
              }
            },
            "description": "Webhook list"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "webhooks:read"
            ]
          }
        ],
        "summary": "List user webhooks",
        "tags": [
          "Webhooks"
        ]
      },
      "post": {
        "description": "Register a new outbound webhook. The plaintext signing secret is returned exactly once — store it securely.\n\nRequires the `webhooks:write` scope — Create, edit, and delete your webhook endpoints.",
        "operationId": "createWebhook",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateWebhook"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookCreateResponse"
                }
              }
            },
            "description": "Webhook created"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          }
        ],
        "summary": "Create a webhook",
        "tags": [
          "Webhooks"
        ]
      }
    },
    "/api/webhooks/{id}": {
      "delete": {
        "description": "Hard-delete a webhook. Cascades to all WebhookCall rows.\n\nRequires the `webhooks:write` scope — Create, edit, and delete your webhook endpoints.",
        "operationId": "deleteWebhook",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEmpty"
                }
              }
            },
            "description": "Webhook deleted"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          }
        ],
        "summary": "Delete a webhook",
        "tags": [
          "Webhooks"
        ]
      },
      "get": {
        "description": "Requires the `webhooks:read` scope — View your webhook endpoints and their delivery history.",
        "operationId": "getWebhook",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookGetResponse"
                }
              }
            },
            "description": "Webhook"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "webhooks:read"
            ]
          }
        ],
        "summary": "Get a webhook",
        "tags": [
          "Webhooks"
        ]
      },
      "patch": {
        "description": "Requires the `webhooks:write` scope — Create, edit, and delete your webhook endpoints.",
        "operationId": "updateWebhook",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateWebhook"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookGetResponse"
                }
              }
            },
            "description": "Webhook updated"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          }
        ],
        "summary": "Update a webhook",
        "tags": [
          "Webhooks"
        ]
      }
    },
    "/api/webhooks/{id}/calls": {
      "get": {
        "description": "Cursor-paginated list of recent delivery attempts for a single webhook.\n\nRequires the `webhooks:read` scope — View your webhook endpoints and their delivery history.",
        "operationId": "listWebhookCalls",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          },
          {
            "in": "query",
            "name": "limit",
            "required": false,
            "schema": {
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "type": "integer"
            }
          },
          {
            "in": "query",
            "name": "cursor",
            "required": false,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookCallsListResponse"
                }
              }
            },
            "description": "Webhook call history"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "webhooks:read"
            ]
          }
        ],
        "summary": "List recent webhook calls",
        "tags": [
          "Webhooks"
        ]
      }
    },
    "/api/webhooks/{id}/rotate-secret": {
      "post": {
        "description": "Generate a new shared secret. Returns the new plaintext secret exactly once.\n\nRequires the `webhooks:write` scope — Create, edit, and delete your webhook endpoints.",
        "operationId": "rotateWebhookSecret",
        "parameters": [
          {
            "in": "path",
            "name": "id",
            "required": true,
            "schema": {
              "format": "uuid",
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookRotateSecretResponse"
                }
              }
            },
            "description": "Secret rotated"
          },
          "400": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Validation error"
          },
          "401": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Unauthorized — missing or invalid auth"
          },
          "403": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Forbidden — insufficient permissions or project disabled"
          },
          "404": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Resource not found"
          },
          "429": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Rate limit or billing limit exceeded"
          },
          "500": {
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "description": "Internal server error"
          }
        },
        "security": [
          {
            "ApiKeyAuth": []
          },
          {
            "SessionAuth": []
          },
          {
            "OAuth2": [
              "webhooks:write"
            ]
          }
        ],
        "summary": "Rotate the webhook signing secret",
        "tags": [
          "Webhooks"
        ]
      }
    }
  },
  "servers": [
    {
      "url": "https://api.sendly.now"
    }
  ],
  "tags": [
    {
      "description": "Send transactional email and inspect deliveries.",
      "name": "Emails"
    },
    {
      "description": "Bulk sends to a list, segment, or filtered audience. Served by the `/api/v1` surface (bare payloads, RFC 9457 errors).",
      "name": "Campaigns"
    },
    {
      "description": "Saved audiences — a `DYNAMIC` filter re-evaluated on read, or a `STATIC` membership list. Served by the `/api/v1` surface (bare payloads, RFC 9457 errors).",
      "name": "Segments"
    },
    {
      "description": "Manage subscribers and per-contact custom data.",
      "name": "Contacts"
    },
    {
      "description": "Self-serve list membership. These two endpoints accept sending-only keys so they can back a public subscribe/unsubscribe form; list management itself is dashboard-only.",
      "name": "Lists"
    },
    {
      "description": "Register sending domains and manage SES verification.",
      "name": "Domains"
    },
    {
      "description": "Reusable email templates referenced by send operations.",
      "name": "Templates"
    },
    {
      "description": "User-managed outbound webhooks for email and contact events.",
      "name": "Webhooks"
    },
    {
      "description": "Project-scoped sending/API credentials. All four operations are documented; minting and rotation answer with a one-time reveal link rather than the secret, so a delegated caller never receives a key it could use.",
      "name": "API Keys"
    },
    {
      "description": "Project-scoped suppression list. Hard bounces and complaints land here automatically.",
      "name": "Suppression"
    },
    {
      "description": "Event-triggered automations and the contact runs through them. Served by the `/api/v1` surface (bare payloads, RFC 9457 errors).",
      "name": "Workflows"
    },
    {
      "description": "Track custom contact events from your application.",
      "name": "Events"
    },
    {
      "description": "Aggregate sending and engagement metrics. Every read is bounded to a window of at most 90 days and cached for 15 minutes.",
      "name": "Analytics"
    },
    {
      "description": "Why mail from one of your domains is or is not arriving: DNS identity, recent delivery outcomes, and one recipient's suppression state, composed into findings.",
      "name": "Deliverability"
    },
    {
      "description": "Current email usage against the monthly and daily limits the platform enforces.",
      "name": "Usage"
    },
    {
      "description": "The project a credential is scoped to, and creating new ones. Configuring an existing project stays a dashboard action.",
      "name": "Projects"
    },
    {
      "description": "Real receiving mailboxes on your verified domains. Creating one changes how that domain's mail is routed; the messages a mailbox receives are not part of this API.",
      "name": "Mailboxes"
    },
    {
      "description": "Open email-validation endpoint (no auth required). Used by the marketing-site verifier.",
      "name": "Verify"
    },
    {
      "description": "Bulk email validation, billed per address: a bounded synchronous batch, and background runs over a whole list. Distinct from `Verify`, which is the one open, unauthenticated, single-address endpoint the marketing site calls.",
      "name": "Validation"
    },
    {
      "description": "The subjects you mail about, and what each contact has said about them. A topic answer is a standing decision rather than an audience filter: it applies whatever audience a campaign selects, so it cannot be routed around by choosing a different one.",
      "name": "Topics"
    }
  ],
  "webhooks": {}
}
