{
  "openapi": "3.0.3",
  "info": {
    "title": "Webtools API",
    "description": "REST API for Webtools — programmatic access to website performance lab tests, uptime monitors, site crawlers, and RUM data. All endpoints require authentication via `X-Email` and `X-API-Key` headers (generated from your profile page), except where noted.",
    "version": "1.0.0",
    "contact": {
      "url": "https://webtools.ayalr.com/api-docs"
    }
  },
  "servers": [
    {
      "url": "https://webtools.ayalr.com/api",
      "description": "Production"
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Your plaintext API key, generated from the Profile page."
      },
      "EmailAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-Email",
        "description": "The email address associated with your Webtools account."
      }
    },
    "schemas": {
      "Meta": {
        "type": "object",
        "properties": {
          "page":     { "type": "integer", "example": 1 },
          "per_page": { "type": "integer", "example": 50 },
          "total":    { "type": "integer", "example": 123 }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "properties": {
          "message": { "type": "string", "example": "Invalid email or API key." }
        }
      },
      "SiteUrl": {
        "type": "object",
        "properties": {
          "id":    { "type": "integer", "example": 3 },
          "url":   { "type": "string",  "example": "https://example.com" },
          "label": { "type": "string",  "example": "Homepage" }
        }
      },
      "Site": {
        "type": "object",
        "properties": {
          "id":         { "type": "integer", "example": 1 },
          "name":       { "type": "string",  "example": "My Website" },
          "user":       { "type": "integer", "example": 42 },
          "created_at": { "type": "string",  "format": "date-time" },
          "updated_at": { "type": "string",  "format": "date-time" },
          "urls": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/SiteUrl" }
          }
        }
      },
      "CrawlSummary": {
        "type": "object",
        "properties": {
          "id":             { "type": "integer", "example": 101 },
          "monitorId":      { "type": "integer", "example": 8 },
          "time":           { "type": "integer", "example": 1746172800000, "description": "Unix timestamp in milliseconds" },
          "success":        { "type": "integer", "enum": [0, 1] },
          "totalCrawled":   { "type": "integer", "example": 142 },
          "securityIssues": { "type": "integer", "example": 0 },
          "non200s":        { "type": "integer", "example": 3 },
          "multipleH1s":    { "type": "integer", "example": 1 },
          "errorMessage":   { "type": "string",  "nullable": true },
          "detail_ref":     { "type": "string",  "example": "/api/getCrawlDetailedResult/101" }
        }
      },
      "Crawler": {
        "type": "object",
        "properties": {
          "id":       { "type": "integer", "example": 8 },
          "name":     { "type": "string",  "example": "Main Site Crawler" },
          "url":      { "type": "string",  "example": "https://example.com" },
          "type":     { "type": "string",  "example": "crawler" },
          "status":   { "type": "string",  "nullable": true },
          "lastRun":  { "type": "integer", "example": 1746172800000 },
          "paused":   { "type": "integer", "enum": [0, 1] },
          "verified": { "type": "integer", "enum": [0, 1] },
          "crawls": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/CrawlSummary" }
          }
        }
      },
      "Monitor": {
        "type": "object",
        "properties": {
          "id":          { "type": "integer", "example": 12 },
          "name":        { "type": "string",  "example": "Homepage Uptime" },
          "type":        { "type": "string",  "example": "website", "enum": ["website", "RUM", "Server"] },
          "url":         { "type": "string",  "nullable": true, "example": "https://example.com" },
          "ipOrDomain":  { "type": "string",  "nullable": true },
          "status":      { "type": "string",  "nullable": true },
          "lastRun":     { "type": "integer", "example": 1746172800000 },
          "paused":      { "type": "integer", "enum": [0, 1] },
          "location":    { "type": "string",  "nullable": true, "example": "MT" }
        }
      },
      "PerformanceReport": {
        "type": "object",
        "properties": {
          "id":                      { "type": "integer", "example": 201 },
          "siteId":                  { "type": "integer", "example": 1 },
          "urlId":                   { "type": "integer", "example": 3 },
          "performance":             { "type": "integer", "example": 87 },
          "accessibility":           { "type": "integer", "example": 95 },
          "seo":                     { "type": "integer", "example": 92 },
          "bestPractices":           { "type": "integer", "example": 100 },
          "firstContentfulPaint":    { "type": "integer", "example": 1200, "description": "ms" },
          "largestContentfulPaint":  { "type": "integer", "example": 2100, "description": "ms" },
          "cumulativeLayoutShift":   { "type": "number",  "example": 0.05 },
          "totalBlockingTime":       { "type": "integer", "example": 120, "description": "ms" },
          "speedIndex":              { "type": "integer", "example": 1800, "description": "ms" },
          "ttfb":                    { "type": "integer", "example": 210, "description": "ms" },
          "time":                    { "type": "integer", "example": 1746172800000 },
          "created_at":              { "type": "string",  "format": "date-time" }
        }
      }
    },
    "parameters": {
      "PageParam": {
        "name": "page",
        "in": "query",
        "schema": { "type": "integer", "default": 1 },
        "description": "Page number (1-based)."
      },
      "PerPageParam": {
        "name": "per_page",
        "in": "query",
        "schema": { "type": "integer", "default": 50, "maximum": 100 },
        "description": "Records per page. Maximum 100."
      },
      "FromParam": {
        "name": "from",
        "in": "query",
        "schema": { "type": "string", "format": "date-time" },
        "description": "Inclusive lower bound (ISO 8601). Example: `2026-01-01T00:00:00Z`"
      },
      "ToParam": {
        "name": "to",
        "in": "query",
        "schema": { "type": "string", "format": "date-time" },
        "description": "Inclusive upper bound (ISO 8601). Example: `2026-01-31T23:59:59Z`"
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Invalid or missing credentials.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "Forbidden": {
        "description": "Free-tier account — paid plan required.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" },
            "example": { "message": "This endpoint requires a paid plan. Please upgrade your account to access the API." }
          }
        }
      },
      "QuotaExceeded": {
        "description": "Monthly API quota exhausted.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" },
            "example": { "message": "Quota exceeded." }
          }
        }
      },
      "UnprocessableEntity": {
        "description": "Invalid query parameters.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      }
    }
  },
  "security": [
    { "ApiKeyAuth": [] },
    { "EmailAuth": [] }
  ],
  "paths": {
    "/getQuota": {
      "get": {
        "summary": "Get API quota status",
        "description": "Returns your plan's total monthly API call allowance, how many calls you have used this month, and how many remain. This endpoint is authenticated but **never counts against your quota**.",
        "operationId": "getQuota",
        "tags": ["Quota"],
        "responses": {
          "200": {
            "description": "Quota information.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "plan_name":       { "type": "string",  "example": "Pro" },
                        "total_calls":     { "type": "integer", "example": 1000 },
                        "used_calls":      { "type": "integer", "example": 42 },
                        "remaining_calls": { "type": "integer", "example": 958 }
                      }
                    },
                    "server_time": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" }
        }
      }
    },
    "/getSites": {
      "get": {
        "summary": "List sites",
        "description": "Returns all configured sites belonging to your account, including their URLs.",
        "operationId": "getSites",
        "tags": ["Sites"],
        "responses": {
          "200": {
            "description": "List of sites.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sites": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Site" }
                    },
                    "server_time": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/QuotaExceeded" }
        }
      }
    },
    "/runSiteTest": {
      "post": {
        "summary": "Trigger a site lab test",
        "description": "Queues a Lighthouse-based lab test for one of your configured sites. The test runs asynchronously. Optionally provide a `webhook_url` to be notified when each URL's test completes. Consumes one on-demand test per URL on the site.",
        "operationId": "runSiteTest",
        "tags": ["Sites"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["site_id"],
                "properties": {
                  "site_id": {
                    "type": "integer",
                    "description": "ID of the site to test. Use `GET /getSites` to find your site IDs.",
                    "example": 1
                  },
                  "webhook_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Optional. Webtools will POST to this URL when each URL's test completes. The body will contain `siteId` and `urlId`.",
                    "example": "https://your-app.com/webhooks/webtools"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Test queued successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "site_id":     { "type": "integer", "example": 1 },
                        "site_name":   { "type": "string",  "example": "My Website" },
                        "urls_queued": { "type": "integer", "example": 2 },
                        "webhook_url": { "type": "string",  "nullable": true }
                      }
                    },
                    "message":     { "type": "string", "example": "Test queued successfully." },
                    "server_time": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/QuotaExceeded" }
        }
      }
    },
    "/getSiteResults/{siteId}": {
      "get": {
        "summary": "Get site lab test results",
        "description": "Returns lab test results for a specific site over the last *x* days, grouped by each URL configured on the site.",
        "operationId": "getSiteResults",
        "tags": ["Sites"],
        "parameters": [
          {
            "name": "siteId",
            "in": "path",
            "required": true,
            "schema": { "type": "integer" },
            "description": "ID of the site. Must belong to your account."
          },
          {
            "name": "days",
            "in": "query",
            "schema": { "type": "integer", "default": 7, "maximum": 90 },
            "description": "Number of days of history to return."
          },
          { "$ref": "#/components/parameters/PageParam" },
          { "$ref": "#/components/parameters/PerPageParam" }
        ],
        "responses": {
          "200": {
            "description": "Site results grouped by URL.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "site_id":   { "type": "integer" },
                        "site_name": { "type": "string" },
                        "days":      { "type": "integer" },
                        "urls": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "url_id":  { "type": "integer" },
                              "url":     { "type": "string" },
                              "label":   { "type": "string" },
                              "meta":    { "$ref": "#/components/schemas/Meta" },
                              "reports": {
                                "type": "array",
                                "items": { "$ref": "#/components/schemas/PerformanceReport" }
                              }
                            }
                          }
                        }
                      }
                    },
                    "server_time": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/QuotaExceeded" }
        }
      }
    },
    "/getCrawlers": {
      "get": {
        "summary": "List crawlers",
        "description": "Returns all crawler monitors belonging to your account, including recent crawl summaries.",
        "operationId": "getCrawlers",
        "tags": ["Crawlers"],
        "responses": {
          "200": {
            "description": "List of crawlers.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "crawlers": {
                      "type": "array",
                      "items": { "$ref": "#/components/schemas/Crawler" }
                    },
                    "server_time": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/QuotaExceeded" }
        }
      }
    },
    "/runCrawl": {
      "post": {
        "summary": "Trigger a crawl",
        "description": "Queues a crawl run for a specific crawler monitor. The crawl runs asynchronously. Optionally provide a `webhook_url` to be notified when the crawl completes. Consumes one ad-hoc crawl from your monthly allowance.",
        "operationId": "runCrawl",
        "tags": ["Crawlers"],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["crawler_id"],
                "properties": {
                  "crawler_id": {
                    "type": "integer",
                    "description": "ID of the crawler monitor to run. Use `GET /getCrawlers` to find your crawler IDs.",
                    "example": 8
                  },
                  "webhook_url": {
                    "type": "string",
                    "format": "uri",
                    "description": "Optional. Webtools will POST to this URL when the crawl completes.",
                    "example": "https://your-app.com/webhooks/crawl-done"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Crawl queued successfully.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "crawler_id":   { "type": "integer", "example": 8 },
                        "crawler_name": { "type": "string",  "example": "Main Site Crawler" },
                        "crawler_url":  { "type": "string",  "example": "https://example.com" },
                        "webhook_url":  { "type": "string",  "nullable": true }
                      }
                    },
                    "message":     { "type": "string", "example": "Crawl queued successfully." },
                    "server_time": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/Forbidden" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/QuotaExceeded" }
        }
      }
    },
    "/getCrawlDetailedResult/{crawlId}": {
      "get": {
        "summary": "Get detailed crawl result",
        "description": "Returns the full raw crawl result for a specific crawl execution, including visited URLs, response codes, H1s, canonicals, mixed content, and a summary. Use the `id` from the `crawls` array in `GET /getCrawlers`.",
        "operationId": "getCrawlDetailedResult",
        "tags": ["Crawlers"],
        "parameters": [
          {
            "name": "crawlId",
            "in": "path",
            "required": true,
            "schema": { "type": "integer" },
            "description": "ID of the crawl execution."
          }
        ],
        "responses": {
          "200": {
            "description": "Detailed crawl result.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id":           { "type": "integer" },
                        "monitorId":    { "type": "integer" },
                        "monitorName":  { "type": "string" },
                        "monitorUrl":   { "type": "string" },
                        "time":         { "type": "integer" },
                        "success":      { "type": "integer", "enum": [0, 1] },
                        "totalCrawled": { "type": "integer" },
                        "securityIssues": { "type": "integer" },
                        "non200s":      { "type": "integer" },
                        "multipleH1s":  { "type": "integer" },
                        "errorMessage": { "type": "string", "nullable": true },
                        "rawResult": {
                          "type": "object",
                          "description": "Full crawl data including visited URLs, response codes, H1s, canonicals, mixed content, and summary.",
                          "properties": {
                            "visited":       { "type": "object" },
                            "responseCodes": { "type": "object" },
                            "contentH1s":    { "type": "object" },
                            "summary": {
                              "type": "object",
                              "properties": {
                                "non200":               { "type": "integer" },
                                "noTitle":              { "type": "integer" },
                                "multipleH1s":          { "type": "integer" },
                                "multipleCanonicals":   { "type": "integer" },
                                "mixedContent":         { "type": "integer" },
                                "error500":             { "type": "integer" },
                                "error400":             { "type": "integer" },
                                "error300":             { "type": "integer" }
                              }
                            }
                          }
                        }
                      }
                    },
                    "server_time": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/QuotaExceeded" }
        }
      }
    },
    "/getMonitors": {
      "get": {
        "summary": "List monitors",
        "description": "Returns all uptime, RUM, and Server monitors belonging to your account, grouped by type.",
        "operationId": "getMonitors",
        "tags": ["Monitors"],
        "responses": {
          "200": {
            "description": "Monitors grouped by type.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "uptime": { "type": "array", "items": { "$ref": "#/components/schemas/Monitor" } },
                        "rum":    { "type": "array", "items": { "$ref": "#/components/schemas/Monitor" } },
                        "server": { "type": "array", "items": { "$ref": "#/components/schemas/Monitor" } }
                      }
                    },
                    "server_time": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/QuotaExceeded" }
        }
      }
    },
    "/getMonitorResults/{monitorId}": {
      "get": {
        "summary": "Get monitor results",
        "description": "Returns test results for a specific monitor. Data source varies by type: `website` returns the last 24 hours of uptime checks; `RUM` and `Server` return metric-server data scoped by `mode`.",
        "operationId": "getMonitorResults",
        "tags": ["Monitors"],
        "parameters": [
          {
            "name": "monitorId",
            "in": "path",
            "required": true,
            "schema": { "type": "integer" },
            "description": "ID of the monitor. Must be of type `website`, `RUM`, or `Server` and belong to your account."
          },
          {
            "name": "mode",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": ["hour", "day", "week"],
              "default": "hour"
            },
            "description": "Time window for RUM and Server monitors. Ignored for website monitors (always returns last 24h)."
          }
        ],
        "responses": {
          "200": {
            "description": "Monitor results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "monitor_id":   { "type": "integer" },
                        "monitor_name": { "type": "string" },
                        "monitor_type": { "type": "string", "enum": ["website", "RUM", "Server"] },
                        "mode":         { "type": "string", "nullable": true },
                        "results":      { "type": "object", "description": "Result payload — structure varies by monitor type." }
                      }
                    },
                    "server_time": { "type": "string", "format": "date-time" }
                  }
                }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "422": { "$ref": "#/components/responses/UnprocessableEntity" },
          "429": { "$ref": "#/components/responses/QuotaExceeded" }
        }
      }
    }
  },
  "tags": [
    { "name": "Quota",    "description": "API quota and plan information." },
    { "name": "Sites",    "description": "Lab test sites and performance reports." },
    { "name": "Crawlers", "description": "Site crawlers and crawl results." },
    { "name": "Monitors", "description": "Uptime, RUM, and Server monitors." }
  ]
}
