{
  "openapi": "3.1.0",
  "info": {
    "title": "YoPingMe API",
    "version": "1.0.0",
    "description": "REST API for the YoPingMe uptime-monitoring dashboard.\n\nAuthentication is cookie-session based: POST /auth/login sets the `yp_session` cookie (HttpOnly, SameSite=Lax) and all authenticated endpoints require it. Bearer tokens are rejected on this surface.\n\nCSRF: every state-changing request (POST/PUT/PATCH/DELETE) must carry an `Origin` header exactly matching the site origin (or same-site fetch metadata); otherwise it is rejected with 403 `forbidden`.\n\nAll responses use a uniform envelope: success is `{\"data\": ...}` (paginated lists add `\"next_cursor\"`), and every error is `{\"error\": {\"code\", \"message\"}}` with a stable machine-readable code.\n\nMachine clients (agents, integrations) should prefer the MCP endpoint at /api/mcp, which is documented separately and authenticates with API keys minted via /api-keys."
  },
  "servers": [
    {
      "url": "https://app.yoping.me/api/v1"
    }
  ],
  "paths": {
    "/auth/signup": {
      "post": {
        "operationId": "signup",
        "summary": "Sign up",
        "description": "Creates an account and mails a verification link. The response is uniform whether or not the email is already registered, so it never reveals account existence. Signups are limited to 5 per IP per hour.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SignupRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MessageResponse"
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/auth/verify-email": {
      "post": {
        "operationId": "verifyEmail",
        "summary": "Verify email",
        "description": "Consumes an emailed verification token and marks the account's email verified. Any bad, expired, or already-used token yields the same 400.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/VerifyEmailRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MessageResponse"
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/auth/login": {
      "post": {
        "operationId": "login",
        "summary": "Log in",
        "description": "Verifies credentials, rotates and sets the `yp_session` cookie, and returns the same bootstrap payload as GET /me. Wrong password, unknown email, and a temporarily locked account all return the same 401. Limited to 10 attempts per IP per minute, plus a progressive per-account lock.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Either fully logged in (password alone was sufficient; the session cookie is set and the /me bootstrap payload is returned) or, for an account with TOTP enrolled, phase 1 only \u2014 no cookie is set yet, and the response instead carries a `totp_token` to submit to POST /auth/login/totp along with a 6-digit code or recovery code.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/DataEnvelope"
                        },
                        {
                          "properties": {
                            "data": {
                              "$ref": "#/components/schemas/Me"
                            }
                          }
                        }
                      ]
                    },
                    {
                      "allOf": [
                        {
                          "$ref": "#/components/schemas/DataEnvelope"
                        },
                        {
                          "properties": {
                            "data": {
                              "$ref": "#/components/schemas/TotpRequired"
                            }
                          }
                        }
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/auth/logout": {
      "post": {
        "operationId": "logout",
        "summary": "Log out",
        "description": "Revokes the current session (if any) and clears the session cookie. Reachable without authentication so a stale cookie can still be cleared.",
        "security": [],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MessageResponse"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/auth/password-reset": {
      "post": {
        "operationId": "requestPasswordReset",
        "summary": "Request password reset",
        "description": "Mails a password-reset link when the email maps to an account. Always returns the same uniform 200, so it never reveals account existence.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PasswordResetRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MessageResponse"
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/auth/password-reset/confirm": {
      "post": {
        "operationId": "confirmPasswordReset",
        "summary": "Confirm password reset",
        "description": "Consumes a reset token and sets the new password (minimum 10 characters). All existing sessions are revoked; the user must sign in again.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PasswordResetConfirmRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MessageResponse"
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/auth/resend-verification": {
      "post": {
        "operationId": "resendVerification",
        "summary": "Resend verification email",
        "description": "Mails a fresh verification link when the email maps to a still-unverified account. Always returns the same uniform 200.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ResendVerificationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MessageResponse"
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/auth/login/totp": {
      "post": {
        "operationId": "loginTotp",
        "summary": "Complete two-phase login with a TOTP or recovery code",
        "description": "Phase 2 of login for accounts with TOTP enrolled: consumes the `totp_token` returned by POST /auth/login's `totp_required` response, verifies a 6-digit TOTP code (or a recovery code), and on success rotates and sets the `yp_session` cookie, returning the same bootstrap payload as GET /me. The token is single-use and expires 5 minutes after phase 1; a wrong code does not burn it, but each code (TOTP step or recovery code) can only ever be consumed once. Limited to 10 login attempts per IP per minute plus 5 code attempts per user per minute.",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginTotpRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Logged in; the session cookie is set and the /me bootstrap payload is returned.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/Me"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/me": {
      "get": {
        "operationId": "getMe",
        "summary": "Get current user",
        "description": "Returns the SPA bootstrap payload: user, account, plan limits, usage, and active region codes. Still reachable while the account is in its deletion grace window.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "The bootstrap payload.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/Me"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/auth/sessions": {
      "get": {
        "operationId": "listSessions",
        "summary": "List sessions",
        "description": "Lists the caller's active sessions with the current one flagged.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "The caller's sessions.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "object",
                          "required": [
                            "sessions"
                          ],
                          "properties": {
                            "sessions": {
                              "type": "array",
                              "items": {
                                "$ref": "#/components/schemas/Session"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/auth/sessions/revoke-others": {
      "post": {
        "operationId": "revokeOtherSessions",
        "summary": "Revoke other sessions",
        "description": "Revokes every session of the caller except the current one (\"log out everywhere else\").",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "Number of sessions revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "object",
                          "required": [
                            "revoked"
                          ],
                          "properties": {
                            "revoked": {
                              "type": "integer"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/auth/totp/setup": {
      "post": {
        "operationId": "totpSetup",
        "summary": "Start two-factor authentication enrollment",
        "description": "Re-verifies the caller's password (a stolen session cookie alone must not be able to enroll 2FA and lock the owner out), then generates a fresh TOTP secret and stores it as pending \u2014 login ignores it until POST /auth/totp/confirm proves the authenticator holds it. Refused with 400 if two-factor authentication is already enabled. Shares the per-user 5 code attempts per minute limiter with confirm and login phase 2.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TOTPSetupRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Pending secret created.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "object",
                          "required": [
                            "otpauth_uri",
                            "secret"
                          ],
                          "properties": {
                            "otpauth_uri": {
                              "type": "string",
                              "description": "otpauth:// URI for the authenticator app to scan or import."
                            },
                            "secret": {
                              "type": "string",
                              "description": "The base32 TOTP secret, for manual entry."
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/auth/totp/confirm": {
      "post": {
        "operationId": "totpConfirm",
        "summary": "Confirm two-factor authentication enrollment",
        "description": "Activates the pending secret from POST /auth/totp/setup once a valid 6-digit code proves the authenticator holds it, and mints a fresh set of 8 recovery codes \u2014 the only time the raw codes are ever returned. 400 if there is no pending setup or the code is invalid. Shares the per-user 5 code attempts per minute limiter with setup and login phase 2.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TOTPConfirmRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Two-factor authentication enabled; raw recovery codes returned once.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "object",
                          "required": [
                            "recovery_codes"
                          ],
                          "properties": {
                            "recovery_codes": {
                              "type": "array",
                              "description": "8 single-use recovery codes; shown only this once.",
                              "items": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/auth/totp/disable": {
      "post": {
        "operationId": "totpDisable",
        "summary": "Disable two-factor authentication",
        "description": "Turns off 2FA after proving possession of the second factor (a valid TOTP code or an unused recovery code) \u2014 a stolen session cookie alone must not be able to strip the account's protection. Clears the secret and deletes all recovery codes. 400 if 2FA is not enabled or the code is invalid. Shares the per-user 5 code attempts per minute limiter with setup, confirm, recovery-codes, and login phase 2.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TOTPDisableRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MessageResponse"
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/auth/totp/recovery-codes": {
      "post": {
        "operationId": "totpRegenerateRecoveryCodes",
        "summary": "Regenerate recovery codes",
        "description": "Mints a fresh set of 8 recovery codes, replacing (and invalidating) the previous set. TOTP code only \u2014 a found recovery code must not be able to mint itself seven new siblings. 400 if 2FA is not enabled or the code is invalid or not a TOTP code. Shares the per-user 5 code attempts per minute limiter with setup, confirm, disable, and login phase 2.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TOTPRecoveryCodesRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "New recovery codes generated; raw codes returned once.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "object",
                          "required": [
                            "recovery_codes"
                          ],
                          "properties": {
                            "recovery_codes": {
                              "type": "array",
                              "description": "8 single-use recovery codes; shown only this once, replacing the previous set.",
                              "items": {
                                "type": "string"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/account/delete": {
      "post": {
        "operationId": "deleteAccount",
        "summary": "Schedule account deletion",
        "description": "Verifies the caller's password and soft-deletes the account with a 7-day grace window during which /account/restore can undo it. An incorrect password returns 400.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AccountDeleteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Deletion scheduled.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "object",
                          "required": [
                            "deleted_at",
                            "purge_after",
                            "message"
                          ],
                          "properties": {
                            "deleted_at": {
                              "type": "string",
                              "format": "date-time"
                            },
                            "purge_after": {
                              "type": "string",
                              "format": "date-time"
                            },
                            "message": {
                              "type": "string"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/account/restore": {
      "post": {
        "operationId": "restoreAccount",
        "summary": "Restore account",
        "description": "Clears the soft-delete marker during the deletion grace window. Reachable while the account is scheduled for deletion.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MessageResponse"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/billing/checkout": {
      "post": {
        "operationId": "billingCheckout",
        "summary": "Start a plan checkout",
        "description": "Creates a Paddle checkout transaction for the requested paid plan and billing interval, and returns the transaction id and a client token for the dashboard to open with Paddle.js as an overlay. The trial is once per account, ever: the backend checks whether the account has any billing history \u2014 including a canceled subscription \u2014 and selects a trial or no-trial price variant accordingly, so only a genuinely first-ever checkout gets the 14-day card-required trial. The plan change itself lands asynchronously via Paddle's webhook after payment; the dashboard polls GET /me until it appears. Returns 400 `validation_failed` when billing is not configured on this deployment, when the plan/interval is unknown, when the account already holds the requested plan, or when it already has a live subscription (a subscriber switches plan through POST /billing/change-plan instead).",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BillingCheckoutRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The Paddle checkout transaction for the dashboard to open with Paddle.js.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/BillingCheckoutResponse"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/billing/portal": {
      "post": {
        "operationId": "billingPortal",
        "summary": "Open the billing portal",
        "description": "Creates a Paddle customer-portal session \u2014 card updates, cancellation, invoices \u2014 and returns its URL for the browser to redirect to. Paddle's portal does not support switching plans; an existing subscriber switches plan through POST /billing/change-plan, and an account with no subscription buys through POST /billing/checkout. Returns 400 `validation_failed` when billing is not configured or the account has no billing customer yet (nothing has ever been purchased).",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "The Billing Portal session URL.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/BillingRedirect"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/billing/change-plan": {
      "post": {
        "operationId": "billingChangePlan",
        "summary": "Switch an existing subscription's plan or interval",
        "description": "Switches a live subscription between Pro and Team, and between monthly and annual, by updating its price at Paddle. The difference is prorated and charged or credited immediately. Paddle's customer portal cannot change plans, so this is the only self-serve path; a customer without a subscription uses POST /billing/checkout instead, and that endpoint refuses an account that already has a live one.\n\nThe no-trial price variant is always used: an account with a live subscription has billing history by definition.\n\nLike checkout, the plan flip lands asynchronously via Paddle's webhook \u2014 this returns `status: \"updating\"` and the dashboard polls GET /me until `billing.plan` and `billing.interval` match.\n\nReturns 400 `validation_failed` when billing is not configured, when the plan/interval is unknown, when the account has no subscription to change (including a comped plan granted by an operator), when the subscription is past due or already cancelling, or when it is already on the requested plan and interval. Returns 422 `plan_limit` when the account's current usage exceeds the target plan's limits; its `error.details.blockers` lists each one so the customer can be told what to trim first.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BillingCheckoutRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The switch was accepted at Paddle; the plan flip follows via webhook.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/BillingChangePlanResponse"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api-keys": {
      "get": {
        "operationId": "listApiKeys",
        "summary": "List API keys",
        "description": "Lists the account's MCP API keys (revoked ones included). Key management requires a browser session; an API-key-authenticated caller gets 403.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "The account's API keys.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ApiKey"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "createApiKey",
        "summary": "Create API key",
        "description": "Mints a new MCP API key. The raw token (prefixed `ypk_`) is returned once, at creation only. At most 10 keys per account (422 `plan_limit` beyond that).",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateApiKeyRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Key created; `token` is the raw key, shown only in this response.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/ApiKeyWithToken"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api-keys/{id}": {
      "delete": {
        "operationId": "revokeApiKey",
        "summary": "Revoke API key",
        "description": "Revokes an API key. A missing or foreign key id returns 404.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "Key revoked.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "object",
                          "required": [
                            "revoked"
                          ],
                          "properties": {
                            "revoked": {
                              "type": "boolean"
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/regions": {
      "get": {
        "operationId": "listRegions",
        "summary": "List regions",
        "description": "Returns the canonical list of active probe regions (code and display name).",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "The active regions, in stable order.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "object",
                          "required": [
                            "regions"
                          ],
                          "properties": {
                            "regions": {
                              "type": "array",
                              "items": {
                                "$ref": "#/components/schemas/Region"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/monitors": {
      "get": {
        "operationId": "listMonitors",
        "summary": "List monitors",
        "description": "Lists the account's monitors as operational summaries (status plus 24h/7d/30d uptime), cursor-paginated in id order.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "One page of monitor summaries.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/MonitorSummary"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "createMonitor",
        "summary": "Create monitor",
        "description": "Creates a monitor. The plan's monitor cap and the per-account distinct-target-host cap (both from the plan) return 422 `plan_limit`; an alert channel id that does not belong to the account returns 404.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MonitorRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Monitor created.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/Monitor"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/monitors/{id}": {
      "get": {
        "operationId": "getMonitor",
        "summary": "Get monitor",
        "description": "Returns the full monitor object plus per-region streak state. A missing or foreign id returns 404.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "The monitor with region states.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/MonitorDetail"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "operationId": "updateMonitor",
        "summary": "Update monitor",
        "description": "Replaces the monitor's configuration (full payload, same validation as create). Cap violations return 422 `plan_limit`; a foreign alert channel id returns 404.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MonitorRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Monitor updated.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/Monitor"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteMonitor",
        "summary": "Delete monitor",
        "description": "Deletes the monitor. A missing or foreign id returns 404.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MessageResponse"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/monitors/{id}/pause": {
      "post": {
        "operationId": "pauseMonitor",
        "summary": "Pause monitor",
        "description": "Pauses checking for the monitor (status resets to `unknown`) and returns the updated monitor.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "Monitor paused.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/Monitor"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/monitors/{id}/resume": {
      "post": {
        "operationId": "resumeMonitor",
        "summary": "Resume monitor",
        "description": "Resumes checking for a paused monitor and returns the updated monitor.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "Monitor resumed.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/Monitor"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/monitors/{id}/channels/{cid}": {
      "post": {
        "operationId": "attachMonitorChannel",
        "summary": "Attach an alert channel to a monitor",
        "description": "Adds one alert channel to this monitor's routing set without otherwise touching its configuration, and returns the updated monitor. Deliberately not PUT /monitors/{id}: that body is a full replace, so it re-validates interval and regions against the current plan (a monitor grandfathered below its plan's floor could not be routed at all) and a stale material field would reset the schedule and the per-region streaks. Idempotent \u2014 attaching an already-attached channel succeeds and changes nothing. A missing or foreign monitor or channel id returns 404; a channel type outside the plan returns 400.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/PathChannelId"
          }
        ],
        "responses": {
          "200": {
            "description": "Channel attached; the monitor's current state.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/Monitor"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "detachMonitorChannel",
        "summary": "Detach an alert channel from a monitor",
        "description": "Removes one alert channel from this monitor's routing set and voids that pair's pending alert jobs, returning the updated monitor. The dispatcher's claim joins alert_jobs to alert_channels and never re-checks monitor_alerts, so an un-voided pending job would still be delivered through a channel the customer just switched off for this monitor. Detaching a channel that is not attached is a no-op success; the 404 is reserved for a missing or foreign monitor.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/PathChannelId"
          }
        ],
        "responses": {
          "200": {
            "description": "Channel detached; the monitor's current state.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/Monitor"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/monitors/{id}/results": {
      "get": {
        "operationId": "listMonitorResults",
        "summary": "List recent check results",
        "description": "Returns raw check results from the last 24 hours, newest first, cursor-paginated (the cursor encodes the last row's execution timestamp).",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "One page of check results.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/CheckResult"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/monitors/{id}/metrics": {
      "get": {
        "operationId": "getMonitorMetrics",
        "summary": "Get monitor metrics",
        "description": "Returns downsampled chart points per region for [from, to), capped at 500 points. Granularity `raw` is allowed only for windows of at most 48 hours. `from` defaults to 24 hours before `to`; `to` defaults to now.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          },
          {
            "name": "granularity",
            "in": "query",
            "description": "Bucket size for the points.",
            "schema": {
              "type": "string",
              "enum": [
                "raw",
                "1h",
                "1d"
              ],
              "default": "1h"
            }
          },
          {
            "name": "from",
            "in": "query",
            "description": "RFC 3339 start of the window (inclusive).",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "RFC 3339 end of the window (exclusive).",
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Chart points for the window.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "object",
                          "required": [
                            "granularity",
                            "points"
                          ],
                          "properties": {
                            "granularity": {
                              "type": "string",
                              "enum": [
                                "raw",
                                "1h",
                                "1d"
                              ]
                            },
                            "points": {
                              "type": "array",
                              "items": {
                                "$ref": "#/components/schemas/MetricPoint"
                              }
                            }
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/incidents": {
      "get": {
        "operationId": "listIncidents",
        "summary": "List incidents",
        "description": "Lists the account's incidents, newest first, cursor-paginated (the cursor encodes the last row's start timestamp). Optionally filtered by status and monitor.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "description": "Filter by incident state.",
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "resolved"
              ]
            }
          },
          {
            "name": "monitor_id",
            "in": "query",
            "description": "Filter to one monitor's incidents.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "One page of incidents.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Incident"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/incidents/{id}/notifications": {
      "get": {
        "operationId": "listIncidentNotifications",
        "summary": "List incident notifications",
        "description": "Returns the alert-delivery timeline for one incident: which channel, which transition, attempts, and outcome. A missing or foreign incident id returns 404.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "The notification timeline, oldest first.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Notification"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/alert_channels": {
      "get": {
        "operationId": "listAlertChannels",
        "summary": "List alert channels",
        "description": "Lists the account's alert channels, cursor-paginated in id order. Configs are redacted: secrets and Slack webhook URLs never appear in responses.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/Limit"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "One page of alert channels.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/ListEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/AlertChannel"
                          }
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "createAlertChannel",
        "summary": "Create alert channel",
        "description": "Creates an email, webhook, or Slack alert channel. An email channel to any address other than the account's own verified email must be confirmed via an emailed link before it can receive alerts; webhook and Slack channels are usable immediately.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AlertChannelRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Channel created (redacted view).",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/AlertChannel"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/alert_channels/{id}": {
      "put": {
        "operationId": "updateAlertChannel",
        "summary": "Update alert channel",
        "description": "Updates a channel's config and/or enabled flag. The channel's type cannot change. Omitted config fields keep their current values; changing an email address resets verification and re-issues a confirm link.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AlertChannelRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Channel updated (redacted view).",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/AlertChannel"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteAlertChannel",
        "summary": "Delete alert channel",
        "description": "Deletes the channel. A missing or foreign id returns 404.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MessageResponse"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/alert_channels/{id}/verify": {
      "post": {
        "operationId": "verifyAlertChannel",
        "summary": "Confirm alert channel",
        "description": "Consumes the emailed confirm-by-link token for an email channel, marking it verified. An invalid or expired token returns 400.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ChannelVerifyRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MessageResponse"
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/alert_channels/{id}/test": {
      "post": {
        "operationId": "testAlertChannel",
        "summary": "Send test alert",
        "description": "Fires a test alert to a verified, enabled channel. The optional body names the monitor the test was fired from. Capped at 5 test alerts per account per hour (429 beyond that); an unverified or disabled channel returns 400.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/TestChannelRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/MessageResponse"
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApiSpec",
        "summary": "Get this OpenAPI document",
        "description": "Returns this OpenAPI 3.1 specification as JSON. No authentication required.",
        "security": [],
        "responses": {
          "200": {
            "description": "The OpenAPI document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/me/status-pages": {
      "get": {
        "operationId": "listStatusPages",
        "summary": "List status pages",
        "description": "Lists the account's status pages plus the current plan's status-page limits.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "responses": {
          "200": {
            "description": "The account's status pages and plan limits.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/StatusPageList"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "post": {
        "operationId": "createStatusPage",
        "summary": "Create status page",
        "description": "Creates a status page. Slug format/reserved-name violations and duplicate/foreign monitor ids are 400 validation_failed; plan-gated fields (hide_branding, custom_domain, monitor count) and the per-plan page cap are 422 plan_limit.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StatusPageRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Status page created.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/StatusPage"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/me/status-pages/slug-check": {
      "get": {
        "operationId": "statusPageSlugCheck",
        "summary": "Check status page slug availability",
        "description": "Reports whether a slug is free to claim. Malformed and reserved slugs are simply `available: false`, never an error.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Availability of the slug.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/StatusPageSlugCheck"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/me/status-pages/{id}": {
      "get": {
        "operationId": "getStatusPage",
        "summary": "Get status page",
        "description": "Fetches one status page. A missing or foreign id returns 404.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "description": "The status page.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/StatusPage"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "put": {
        "operationId": "updateStatusPage",
        "summary": "Update status page",
        "description": "Replaces a status page's fields, including its monitor list. A missing or foreign id returns 404. Only fields this request actually changes are checked against the current plan \u2014 a preference set on a higher plan and left untouched survives a downgrade.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StatusPageRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Status page updated.",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/DataEnvelope"
                    },
                    {
                      "properties": {
                        "data": {
                          "$ref": "#/components/schemas/StatusPage"
                        }
                      }
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/PlanLimit"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      },
      "delete": {
        "operationId": "deleteStatusPage",
        "summary": "Delete status page",
        "description": "Deletes a status page. A missing or foreign id returns 404.",
        "security": [
          {
            "sessionCookie": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/PathId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/MessageResponse"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "sessionCookie": {
        "type": "apiKey",
        "in": "cookie",
        "name": "yp_session",
        "description": "Session cookie set by POST /auth/login (HttpOnly, SameSite=Lax). State-changing requests must also send an Origin header matching the site origin (CSRF)."
      }
    },
    "parameters": {
      "PathId": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Resource id. Ids belonging to another account behave as missing (404).",
        "schema": {
          "type": "string"
        }
      },
      "PathChannelId": {
        "name": "cid",
        "in": "path",
        "required": true,
        "description": "Alert channel id. Ids belonging to another account behave as missing (404).",
        "schema": {
          "type": "string"
        }
      },
      "Limit": {
        "name": "limit",
        "in": "query",
        "description": "Page size. Defaults to 50; values above 100 are clamped to 100. Non-numeric or non-positive values are a 400.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 100,
          "default": 50
        }
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "description": "Opaque pagination cursor from a previous response's `next_cursor`. Malformed cursors are a 400.",
        "schema": {
          "type": "string"
        }
      }
    },
    "responses": {
      "MessageResponse": {
        "description": "Success with a human-readable message.",
        "content": {
          "application/json": {
            "schema": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/DataEnvelope"
                },
                {
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/MessageData"
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "ValidationError": {
        "description": "Invalid input (code `validation_failed`): malformed JSON, a missing or out-of-range field, bad pagination parameters, or a rejected token.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Not authenticated (code `unauthorized`): missing, invalid, or expired session; invalid login credentials; or a Bearer token, which this surface rejects.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Forbidden. Code `forbidden` for a cross-origin (CSRF) request blocked by origin validation or for API-key management attempted without a browser session; `account_suspended` when a suspended account attempts a mutation; `account_deleted` when an account in its deletion grace window calls a non-exempt route.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "NotFound": {
        "description": "Not found (code `not_found`). A resource id belonging to another account is indistinguishable from a missing one.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "PlanLimit": {
        "description": "A plan or account cap was hit (code `plan_limit`): monitor cap, distinct-target-host cap, or API-key cap.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Too many requests (code `rate_limited`). A global per-IP budget of 300 requests/minute (burst 60) applies to every endpoint; login, signup, and test alerts have tighter dedicated limits.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      }
    },
    "schemas": {
      "DataEnvelope": {
        "type": "object",
        "description": "Uniform success envelope: the payload is always under `data`.",
        "required": [
          "data"
        ],
        "properties": {
          "data": {}
        }
      },
      "ListEnvelope": {
        "type": "object",
        "description": "Cursor-paginated list envelope. `next_cursor` is an opaque token for the next page, or null on the last page.",
        "required": [
          "data",
          "next_cursor"
        ],
        "properties": {
          "data": {
            "type": "array"
          },
          "next_cursor": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "description": "Uniform error envelope with a stable machine-readable code.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "validation_failed",
                  "unauthorized",
                  "forbidden",
                  "account_suspended",
                  "account_deleted",
                  "not_found",
                  "plan_limit",
                  "rate_limited",
                  "internal"
                ]
              },
              "message": {
                "type": "string"
              },
              "details": {
                "type": "object",
                "description": "Machine-readable payload for the errors a client must act on rather than just display. Absent from every other error. Today only POST /billing/change-plan sets it, as `{\"blockers\": [PlanBlocker, ...]}`.",
                "properties": {
                  "blockers": {
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/PlanBlocker"
                    }
                  }
                }
              }
            }
          }
        }
      },
      "MessageData": {
        "type": "object",
        "required": [
          "message"
        ],
        "properties": {
          "message": {
            "type": "string"
          }
        }
      },
      "SignupRequest": {
        "type": "object",
        "required": [
          "email",
          "password",
          "accept_terms"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "password": {
            "type": "string",
            "minLength": 10
          },
          "account_name": {
            "type": "string",
            "description": "Display name for the account; defaults to the local part of the email."
          },
          "accept_terms": {
            "type": "boolean",
            "description": "Must be true: explicit acceptance of the terms of service and privacy policy. Acceptance time and client IP are recorded."
          }
        }
      },
      "VerifyEmailRequest": {
        "type": "object",
        "required": [
          "token"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "The token from the verification email."
          }
        }
      },
      "LoginRequest": {
        "type": "object",
        "required": [
          "email",
          "password"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "password": {
            "type": "string"
          }
        }
      },
      "LoginTotpRequest": {
        "type": "object",
        "required": [
          "totp_token",
          "code"
        ],
        "properties": {
          "totp_token": {
            "type": "string",
            "description": "The token returned as `totp_token` by POST /auth/login's phase-1 `totp_required` response."
          },
          "code": {
            "type": "string",
            "description": "A 6-digit TOTP code from the enrolled authenticator, or an unused recovery code."
          }
        }
      },
      "TotpRequired": {
        "type": "object",
        "description": "Phase-1 login response for an account with TOTP enrolled: credentials were valid but no session cookie is set yet. Submit `totp_token` plus a code to POST /auth/login/totp to finish logging in.",
        "required": [
          "totp_required",
          "totp_token"
        ],
        "properties": {
          "totp_required": {
            "type": "boolean",
            "enum": [
              true
            ]
          },
          "totp_token": {
            "type": "string",
            "description": "Single-use, expires 5 minutes after phase 1. Pass to POST /auth/login/totp as `totp_token`."
          }
        }
      },
      "PasswordResetRequest": {
        "type": "object",
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          }
        }
      },
      "PasswordResetConfirmRequest": {
        "type": "object",
        "required": [
          "token",
          "new_password"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "The token from the reset email."
          },
          "new_password": {
            "type": "string",
            "minLength": 10
          }
        }
      },
      "ResendVerificationRequest": {
        "type": "object",
        "required": [
          "email"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          }
        }
      },
      "AccountDeleteRequest": {
        "type": "object",
        "required": [
          "password"
        ],
        "properties": {
          "password": {
            "type": "string",
            "description": "The caller's current password, re-verified before deletion."
          }
        }
      },
      "TOTPSetupRequest": {
        "type": "object",
        "required": [
          "password"
        ],
        "properties": {
          "password": {
            "type": "string",
            "description": "The caller's current password, re-verified before starting enrollment so a stolen session cookie alone cannot enable two-factor authentication."
          }
        }
      },
      "TOTPConfirmRequest": {
        "type": "object",
        "required": [
          "code"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "A 6-digit TOTP code generated from the pending secret returned by POST /auth/totp/setup."
          }
        }
      },
      "TOTPDisableRequest": {
        "type": "object",
        "required": [
          "code"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "A 6-digit TOTP code or an unused recovery code proving possession of the second factor."
          }
        }
      },
      "TOTPRecoveryCodesRequest": {
        "type": "object",
        "required": [
          "code"
        ],
        "properties": {
          "code": {
            "type": "string",
            "description": "A 6-digit TOTP code. Recovery codes are not accepted for regeneration."
          }
        }
      },
      "Me": {
        "type": "object",
        "description": "The SPA bootstrap payload, returned by GET /me and POST /auth/login.",
        "required": [
          "user",
          "account",
          "limits",
          "usage",
          "regions",
          "billing"
        ],
        "properties": {
          "user": {
            "type": "object",
            "required": [
              "id",
              "email",
              "email_verified",
              "totp_enabled",
              "created_at"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email"
              },
              "email_verified": {
                "type": "boolean"
              },
              "totp_enabled": {
                "type": "boolean",
                "description": "Whether two-factor authentication (TOTP) is enrolled and confirmed for this account."
              },
              "created_at": {
                "type": "string",
                "format": "date-time"
              }
            }
          },
          "account": {
            "type": "object",
            "required": [
              "id",
              "name",
              "plan",
              "status",
              "deleted_at"
            ],
            "properties": {
              "id": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "plan": {
                "type": "string",
                "enum": [
                  "free",
                  "pro",
                  "team"
                ]
              },
              "status": {
                "type": "string"
              },
              "deleted_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time",
                "description": "Set while the account is in its deletion grace window."
              }
            }
          },
          "limits": {
            "type": "object",
            "description": "The plan's enforced caps.",
            "required": [
              "max_monitors",
              "min_interval_seconds",
              "max_regions",
              "allowed_channel_types",
              "result_retention_days",
              "mcp_history_days",
              "mcp_queries_per_day",
              "max_alert_after_seconds",
              "max_region_alert_after_seconds"
            ],
            "properties": {
              "max_monitors": {
                "type": "integer"
              },
              "min_interval_seconds": {
                "type": "integer"
              },
              "max_regions": {
                "type": "integer",
                "description": "Regions per monitor; the effective cap is min(max_regions, active fleet size)."
              },
              "allowed_channel_types": {
                "type": "array",
                "items": {
                  "type": "string",
                  "enum": [
                    "email",
                    "webhook",
                    "slack"
                  ]
                }
              },
              "result_retention_days": {
                "type": "integer"
              },
              "mcp_history_days": {
                "type": "integer",
                "description": "MCP history window in days; 0 means unlimited."
              },
              "mcp_queries_per_day": {
                "type": "integer"
              },
              "max_alert_after_seconds": {
                "type": "integer",
                "description": "Per-monitor DOWN-alert delay cap in seconds; 0 means the knob is not available on this plan."
              },
              "max_region_alert_after_seconds": {
                "type": "integer",
                "description": "Per-monitor region-degradation alert delay cap in seconds; 0 means the knob is not available on this plan."
              }
            }
          },
          "usage": {
            "type": "object",
            "required": [
              "monitors"
            ],
            "properties": {
              "monitors": {
                "type": "integer",
                "description": "Current monitor count."
              }
            }
          },
          "regions": {
            "type": "array",
            "description": "Active region codes; resolve display names via GET /regions.",
            "items": {
              "type": "string"
            }
          },
          "billing": {
            "type": "object",
            "description": "Billing summary. When `configured` is false (billing disabled on this deployment) every other field is its zero value and the dashboard hides billing UI. Subscription fields are null until the account has a subscription.",
            "required": [
              "configured",
              "has_subscription",
              "status",
              "plan",
              "interval",
              "current_period_end",
              "trial_end",
              "cancel_at_period_end",
              "trial_available",
              "can_manage"
            ],
            "properties": {
              "configured": {
                "type": "boolean"
              },
              "has_subscription": {
                "type": "boolean"
              },
              "status": {
                "type": [
                  "string",
                  "null"
                ],
                "description": "Paddle subscription status verbatim (e.g. trialing, active, past_due, canceled)."
              },
              "plan": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "pro",
                  "team",
                  null
                ]
              },
              "interval": {
                "type": [
                  "string",
                  "null"
                ],
                "enum": [
                  "monthly",
                  "annual",
                  null
                ]
              },
              "current_period_end": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "trial_end": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "cancel_at_period_end": {
                "type": "boolean"
              },
              "trial_available": {
                "type": "boolean",
                "description": "True when the account has never had a subscription; the first checkout gets a 14-day trial."
              },
              "can_manage": {
                "type": "boolean",
                "description": "True when a billing customer exists, so POST /billing/portal will succeed."
              }
            }
          },
          "recovery_codes_remaining": {
            "type": "integer",
            "description": "Count of unused two-factor recovery codes. Present only when totp_enabled is true."
          }
        }
      },
      "Session": {
        "type": "object",
        "required": [
          "id",
          "created_at",
          "last_seen_at",
          "current"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_seen_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "current": {
            "type": "boolean",
            "description": "True for the session making this request."
          }
        }
      },
      "CreateApiKeyRequest": {
        "type": "object",
        "required": [
          "name"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 100
          },
          "write": {
            "type": "boolean",
            "default": false,
            "description": "Also grant the 'mcp:write' scope, which admits the key to the MCP write tools such as create_monitor. Off by default."
          }
        }
      },
      "ApiKey": {
        "type": "object",
        "required": [
          "id",
          "name",
          "prefix",
          "scopes",
          "created_at",
          "last_used_at",
          "revoked_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "prefix": {
            "type": "string",
            "description": "First 12 characters of the raw key (\"ypk_\" plus 8), for display."
          },
          "scopes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Scopes the key carries. Always includes 'mcp'; 'mcp:write' additionally admits it to the MCP write tools."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "last_used_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "revoked_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "ApiKeyWithToken": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ApiKey"
          },
          {
            "type": "object",
            "required": [
              "token"
            ],
            "properties": {
              "token": {
                "type": "string",
                "description": "The raw API key (\"ypk_\"-prefixed). Returned only once, at creation."
              }
            }
          }
        ]
      },
      "Region": {
        "type": "object",
        "required": [
          "code",
          "name"
        ],
        "properties": {
          "code": {
            "type": "string",
            "enum": [
              "eu-central",
              "us-east",
              "ap-southeast"
            ]
          },
          "name": {
            "type": "string"
          }
        }
      },
      "MonitorRequest": {
        "type": "object",
        "description": "Create/update payload for a monitor. Target shape depends on type: http/keyword require an absolute http(s) URL; ssl takes a host, host:port, or https URL (normalized to host:port, default port 443); ping takes a bare hostname or IP; tcp requires host:port.",
        "required": [
          "type",
          "target"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "http",
              "keyword",
              "ssl",
              "ping",
              "tcp"
            ]
          },
          "name": {
            "type": "string",
            "maxLength": 100,
            "description": "Defaults to the target host when omitted."
          },
          "target": {
            "type": "string",
            "maxLength": 2000
          },
          "http_method": {
            "type": "string",
            "enum": [
              "GET",
              "HEAD",
              "POST"
            ],
            "default": "GET"
          },
          "interval_seconds": {
            "type": "integer",
            "maximum": 86400,
            "description": "Minimum and default are the plan's min_interval_seconds (300 on free, 60 on pro)."
          },
          "timeout_ms": {
            "type": "integer",
            "minimum": 1000,
            "maximum": 30000,
            "default": 10000
          },
          "accepted_statuses": {
            "type": "string",
            "default": "2xx,3xx",
            "description": "Comma-separated tokens: a class (2xx-5xx), a 3-digit code (100-599), or an inclusive NNN-NNN range."
          },
          "keyword": {
            "type": "string",
            "minLength": 1,
            "maxLength": 200,
            "description": "Required for type \"keyword\"."
          },
          "keyword_mode": {
            "type": "string",
            "enum": [
              "present",
              "absent"
            ],
            "description": "Required for type \"keyword\"."
          },
          "alert_channel_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Alert channels to attach. A channel id not owned by the account is a 404."
          },
          "regions": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "Region codes to check from (see GET /regions). Omitted, null, or empty means automatic: the first max_regions active regions, tracked as the fleet changes. An explicit set is capped by the plan's max_regions and pinned \u2014 it does not adopt regions added later."
          },
          "notification_policy": {
            "type": "object",
            "properties": {
              "alert_after_seconds": {
                "type": "integer",
                "minimum": 0,
                "description": "0 = alert immediately once the outage is confirmed (default). Maximum is the plan's max_alert_after_seconds."
              },
              "region_alert_after_seconds": {
                "type": "integer",
                "minimum": 0,
                "description": "0 = alert immediately once a region is confirmed degraded (default). Maximum is the plan's max_region_alert_after_seconds. Only meaningful with at least 2 effective regions — a monitor checked from a single region can never see one region disagree with the others, so this can never fire there."
              }
            }
          }
        }
      },
      "Monitor": {
        "type": "object",
        "description": "The full monitor object (returned by create, update, pause, and resume).",
        "required": [
          "id",
          "type",
          "name",
          "target",
          "http_method",
          "interval_seconds",
          "timeout_ms",
          "accepted_statuses",
          "keyword",
          "keyword_mode",
          "paused",
          "paused_reason",
          "current_status",
          "next_due_at",
          "created_at",
          "alert_channel_ids",
          "regions",
          "notification_policy"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "http",
              "keyword",
              "ssl",
              "ping",
              "tcp"
            ]
          },
          "name": {
            "type": "string"
          },
          "target": {
            "type": "string"
          },
          "http_method": {
            "type": "string",
            "enum": [
              "GET",
              "HEAD",
              "POST"
            ]
          },
          "interval_seconds": {
            "type": "integer"
          },
          "timeout_ms": {
            "type": "integer"
          },
          "accepted_statuses": {
            "type": "string"
          },
          "keyword": {
            "type": [
              "string",
              "null"
            ]
          },
          "keyword_mode": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "present",
              "absent",
              null
            ]
          },
          "paused": {
            "type": "boolean"
          },
          "paused_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "domain_conflict",
              "plugin_silent",
              null
            ],
            "description": "Why the system, not the user, paused this monitor; null while it is running or when the user paused it. domain_conflict: disabled because another account monitors this website's domain (free plan). plugin_silent: paused because the YoPingMe WordPress plugin on this site stopped reporting for 30 days; the plugin's next report resumes it automatically."
          },
          "current_status": {
            "type": "string",
            "enum": [
              "up",
              "down",
              "confirming",
              "unknown"
            ]
          },
          "next_due_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "alert_channel_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "regions": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "string"
            },
            "description": "Explicit region codes, or null for automatic (the plan-capped prefix of the active fleet)."
          },
          "notification_policy": {
            "type": "object",
            "required": [
              "alert_after_seconds",
              "region_alert_after_seconds"
            ],
            "properties": {
              "alert_after_seconds": {
                "type": "integer"
              },
              "region_alert_after_seconds": {
                "type": "integer",
                "description": "Region-degradation alert delay in seconds. Only meaningful with at least 2 effective regions — a monitor checked from a single region can never see one region disagree with the others, so this can never fire there."
              }
            },
            "description": "v1 shipped this as a reserved empty object; it now carries the DOWN-alert delay and the region-degradation alert delay (seconds) and may grow more fields."
          }
        }
      },
      "MonitorDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Monitor"
          },
          {
            "type": "object",
            "required": [
              "region_states"
            ],
            "properties": {
              "region_states": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/MonitorRegionState"
                }
              }
            }
          }
        ]
      },
      "MonitorRegionState": {
        "type": "object",
        "required": [
          "region",
          "fail_streak",
          "ok_streak",
          "last_check"
        ],
        "properties": {
          "region": {
            "type": "string"
          },
          "fail_streak": {
            "type": "integer"
          },
          "ok_streak": {
            "type": "integer"
          },
          "last_check": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "MonitorSummary": {
        "type": "object",
        "description": "One row of the monitors list: operational summary plus uptime windows. Uptime fields are 0..1 fractions, null when no data.",
        "required": [
          "id",
          "type",
          "name",
          "target",
          "interval_seconds",
          "paused",
          "paused_reason",
          "current_status",
          "alert_channel_ids",
          "uptime_24h",
          "uptime_7d",
          "uptime_30d"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "http",
              "keyword",
              "ssl",
              "ping",
              "tcp"
            ]
          },
          "name": {
            "type": "string"
          },
          "target": {
            "type": "string"
          },
          "interval_seconds": {
            "type": "integer"
          },
          "paused": {
            "type": "boolean"
          },
          "paused_reason": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "domain_conflict",
              "plugin_silent",
              null
            ],
            "description": "Why the system, not the user, paused this monitor; null while it is running or when the user paused it. domain_conflict: disabled because another account monitors this website's domain (free plan). plugin_silent: paused because the YoPingMe WordPress plugin on this site stopped reporting for 30 days; the plugin's next report resumes it automatically."
          },
          "current_status": {
            "type": "string",
            "enum": [
              "up",
              "down",
              "confirming",
              "unknown"
            ]
          },
          "alert_channel_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "uptime_24h": {
            "type": [
              "number",
              "null"
            ]
          },
          "uptime_7d": {
            "type": [
              "number",
              "null"
            ]
          },
          "uptime_30d": {
            "type": [
              "number",
              "null"
            ]
          }
        }
      },
      "CheckResult": {
        "type": "object",
        "required": [
          "executed_at",
          "region",
          "ok",
          "status_code",
          "response_time_ms",
          "error_kind"
        ],
        "properties": {
          "executed_at": {
            "type": "string",
            "format": "date-time"
          },
          "region": {
            "type": "string"
          },
          "ok": {
            "type": "boolean"
          },
          "status_code": {
            "type": [
              "integer",
              "null"
            ]
          },
          "response_time_ms": {
            "type": [
              "integer",
              "null"
            ]
          },
          "error_kind": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "MetricPoint": {
        "type": "object",
        "description": "One downsampled chart point for a region. uptime_pct is a 0..1 fraction.",
        "required": [
          "bucket",
          "region",
          "uptime_pct",
          "avg_ms",
          "p95_ms"
        ],
        "properties": {
          "bucket": {
            "type": "string",
            "format": "date-time"
          },
          "region": {
            "type": "string"
          },
          "uptime_pct": {
            "type": [
              "number",
              "null"
            ]
          },
          "avg_ms": {
            "type": [
              "number",
              "null"
            ]
          },
          "p95_ms": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "Incident": {
        "type": "object",
        "required": [
          "id",
          "monitor_id",
          "started_at",
          "resolved_at",
          "cause",
          "kind"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "monitor_id": {
            "type": "string"
          },
          "started_at": {
            "type": "string",
            "format": "date-time"
          },
          "resolved_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Null while the incident is open."
          },
          "cause": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "down",
              "degraded"
            ],
            "description": "down = every checked region agrees the target is unreachable. degraded = one region has been failing continuously while others see it up (design 2026-08-26)."
          },
          "region": {
            "type": "string",
            "description": "The region that is failing. Present only for kind = degraded; omitted entirely for kind = down, where no single region is singled out."
          }
        }
      },
      "Notification": {
        "type": "object",
        "description": "One alert-delivery record in an incident's timeline.",
        "required": [
          "channel_label",
          "transition",
          "state",
          "attempts",
          "last_attempt_at",
          "sent_at",
          "failure_reason",
          "created_at"
        ],
        "properties": {
          "channel_label": {
            "type": "string"
          },
          "transition": {
            "type": "string",
            "enum": [
              "down",
              "up",
              "still_down",
              "degraded",
              "degraded_recovered"
            ],
            "description": "down/up/still_down are whole-monitor alerts. degraded/degraded_recovered are region-degradation alerts (design 2026-08-26); the outbound webhook payload for these also carries a region field naming which region is affected."
          },
          "state": {
            "type": "string",
            "enum": [
              "pending",
              "sent",
              "failed",
              "voided"
            ]
          },
          "attempts": {
            "type": "integer"
          },
          "last_attempt_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "sent_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "failure_reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AlertChannelRequest": {
        "type": "object",
        "description": "Create/update payload for an alert channel. `config` is a union: only the fields for the channel's type are read (email: address; webhook: url and optional secret; slack: webhook_url). On update the type is fixed and omitted config fields keep their current values.",
        "required": [
          "type",
          "config"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "email",
              "webhook",
              "slack"
            ]
          },
          "config": {
            "type": "object",
            "properties": {
              "address": {
                "type": "string",
                "format": "email",
                "description": "Email channels: destination address."
              },
              "url": {
                "type": "string",
                "description": "Webhook channels: absolute http(s) URL with no userinfo."
              },
              "secret": {
                "type": "string",
                "minLength": 16,
                "maxLength": 64,
                "description": "Webhook channels: optional signing secret; a random one is generated when omitted at create."
              },
              "webhook_url": {
                "type": "string",
                "description": "Slack channels: an https://hooks.slack.com/... URL."
              }
            }
          },
          "enabled": {
            "type": "boolean",
            "description": "Update only: omit to keep the current value."
          }
        }
      },
      "AlertChannel": {
        "type": "object",
        "description": "Redacted channel view. `config` depends on type: email has `address`; webhook has `url` and boolean `has_secret`; slack has a redacted `url` like \"hooks.slack.com/\u2026XXXX\". Secrets and full Slack URLs never appear.",
        "required": [
          "id",
          "type",
          "verified_at",
          "enabled",
          "suppressed",
          "consecutive_failures",
          "config"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "email",
              "webhook",
              "slack"
            ]
          },
          "verified_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Null until the channel is confirmed; alerts are only sent to verified channels."
          },
          "enabled": {
            "type": "boolean"
          },
          "suppressed": {
            "type": "boolean"
          },
          "consecutive_failures": {
            "type": "integer"
          },
          "config": {
            "type": "object",
            "properties": {
              "address": {
                "type": "string"
              },
              "url": {
                "type": "string"
              },
              "has_secret": {
                "type": "boolean"
              }
            }
          }
        }
      },
      "ChannelVerifyRequest": {
        "type": "object",
        "required": [
          "token"
        ],
        "properties": {
          "token": {
            "type": "string",
            "description": "The token from the channel confirmation email."
          }
        }
      },
      "TestChannelRequest": {
        "type": "object",
        "properties": {
          "monitor_id": {
            "type": "string",
            "description": "Optional monitor the test was fired from; the test alert names that monitor. An unknown or foreign id is ignored."
          }
        }
      },
      "BillingCheckoutRequest": {
        "type": "object",
        "required": [
          "plan",
          "interval"
        ],
        "properties": {
          "plan": {
            "type": "string",
            "enum": [
              "pro",
              "team"
            ]
          },
          "interval": {
            "type": "string",
            "enum": [
              "monthly",
              "annual"
            ]
          }
        }
      },
      "BillingRedirect": {
        "type": "object",
        "description": "A page to redirect the browser to \u2014 the Paddle customer portal.",
        "required": [
          "url"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "BillingCheckoutResponse": {
        "type": "object",
        "description": "A Paddle checkout transaction for the dashboard to open with Paddle.js as an overlay, rather than a URL to redirect to.",
        "required": [
          "transactionId",
          "clientToken",
          "environment"
        ],
        "properties": {
          "transactionId": {
            "type": "string",
            "description": "The Paddle transaction id to pass to Paddle.js's Checkout.open()."
          },
          "clientToken": {
            "type": "string",
            "description": "Paddle's public client-side token. Safe to expose in the response; only ever handed to an authenticated account that is actually upgrading."
          },
          "environment": {
            "type": "string",
            "enum": [
              "sandbox",
              "live"
            ],
            "description": "Which Paddle environment the token belongs to. Paddle.js defaults to production and rejects a sandbox client token, so a client must call Paddle.Environment.set('sandbox') before Paddle.Initialize when this is \"sandbox\"."
          }
        }
      },
      "BillingChangePlanResponse": {
        "type": "object",
        "required": [
          "plan",
          "interval",
          "status"
        ],
        "properties": {
          "plan": {
            "type": "string",
            "enum": [
              "pro",
              "team"
            ],
            "description": "The plan the subscription is moving to."
          },
          "interval": {
            "type": "string",
            "enum": [
              "monthly",
              "annual"
            ],
            "description": "The billing interval it is moving to."
          },
          "status": {
            "type": "string",
            "enum": [
              "updating"
            ],
            "description": "Always `updating`: Paddle accepted the change and the webhook will apply it."
          }
        }
      },
      "PlanBlocker": {
        "type": "object",
        "description": "One target-plan limit the account's current usage exceeds, returned inside `error.details.blockers` of a 422 `plan_limit` response from POST /billing/change-plan.",
        "required": [
          "kind"
        ],
        "properties": {
          "kind": {
            "type": "string",
            "enum": [
              "monitors",
              "hosts",
              "interval",
              "channel_type"
            ]
          },
          "current": {
            "type": "integer",
            "description": "Current usage: a count for `monitors`/`hosts`, seconds for `interval`."
          },
          "max": {
            "type": "integer",
            "description": "The target plan's cap, for `monitors` and `hosts`."
          },
          "min": {
            "type": "integer",
            "description": "The target plan's interval floor in seconds, for `interval`."
          },
          "type": {
            "type": "string",
            "description": "The channel type unavailable on the target plan, for `channel_type`."
          }
        }
      },
      "StatusPageRequest": {
        "type": "object",
        "required": [
          "slug"
        ],
        "properties": {
          "slug": {
            "type": "string",
            "description": "3-63 lowercase letters, digits, or hyphens; no leading/trailing hyphen. Reserved names (status, api, app, admin, www, mail, statuspage, yopingme, help, support, docs, blog) are rejected."
          },
          "enabled": {
            "type": "boolean",
            "default": false
          },
          "title": {
            "type": "string"
          },
          "hide_branding": {
            "type": "boolean",
            "default": false,
            "description": "Requires a plan with hide_branding (422 plan_limit otherwise)."
          },
          "custom_domain": {
            "type": "string",
            "nullable": true,
            "description": "Requires a plan with custom_domain (422 plan_limit otherwise)."
          },
          "monitor_ids": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Monitors shown on the page, in order. Duplicates and unknown/foreign monitor ids are rejected."
          }
        }
      },
      "StatusPage": {
        "type": "object",
        "required": [
          "id",
          "slug",
          "enabled",
          "title",
          "hide_branding",
          "custom_domain",
          "monitor_ids",
          "public_url",
          "created_at"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "slug": {
            "type": "string"
          },
          "enabled": {
            "type": "boolean"
          },
          "title": {
            "type": "string"
          },
          "hide_branding": {
            "type": "boolean"
          },
          "custom_domain": {
            "type": "string",
            "nullable": true
          },
          "monitor_ids": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "public_url": {
            "type": "string",
            "nullable": true,
            "description": "Null when the box has no STATUS_PAGE_BASE_URL configured."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "StatusPageLimits": {
        "type": "object",
        "required": [
          "max_pages",
          "max_monitors",
          "hide_branding",
          "incidents",
          "regions",
          "custom_domain"
        ],
        "properties": {
          "max_pages": {
            "type": "integer"
          },
          "max_monitors": {
            "type": "integer",
            "description": "0 = unlimited."
          },
          "hide_branding": {
            "type": "boolean"
          },
          "incidents": {
            "type": "boolean"
          },
          "regions": {
            "type": "boolean"
          },
          "custom_domain": {
            "type": "boolean"
          }
        }
      },
      "StatusPageList": {
        "type": "object",
        "required": [
          "pages",
          "limits"
        ],
        "properties": {
          "pages": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StatusPage"
            }
          },
          "limits": {
            "$ref": "#/components/schemas/StatusPageLimits"
          }
        }
      },
      "StatusPageSlugCheck": {
        "type": "object",
        "required": [
          "available"
        ],
        "properties": {
          "available": {
            "type": "boolean"
          }
        }
      }
    }
  }
}
