{"openapi":"3.1.0","info":{"title":"PMM Tools API","version":"1.0.0","summary":"Competitor website captures (markdown and screenshots) for your AI.","description":"The PMM Tools HTTP API: track competitors, curate their URL pools, start and upload captures, and read the results. Most agents should use the MCP server at POST /api/mcp, which offers the same competitor data as tools. Authenticate with \"Authorization: Bearer <connection key>\" (create one in the app; it is the same key the MCP server takes), or with an OAuth access token from the authorization server described at /.well-known/oauth-authorization-server (OpenID Connect discovery: /.well-known/openid-configuration). Errors are JSON: always an error code, and from the shared gates a message and a hint too.\n\nVersioning: every /api response carries an \"API-Version\" header naming the version that answered (today \"1\"). Clients may send \"API-Version: 1\" to pin it; version 1 is the only version, so the server answers version 1 whatever the header says. Within a version, changes are additive only (new operations, new optional parameters, new response fields); anything that would break a client ships as a new version, and existing paths do not move.\n\nDeprecation: a deprecated operation is marked \"deprecated: true\" in /openapi.json, and its responses carry a \"Deprecation\" header (RFC 9745) and, once a removal date is set, a \"Sunset\" header (RFC 8594) at least 90 days after the deprecation date. It keeps working until the Sunset date. No operation is deprecated today.\n\nRate limits: calls with a connection key share one per-account hourly budget sized by plan (Starter 30, Growth 60, Pro 90, Business 120, Scale 120, Team 180 requests an hour), and some operations (captures, runs, imports, AI) also have their own budget. A refused call answers 429 with Retry-After; 429s from the shared limiters also carry the IETF RateLimit-Policy and RateLimit fields (and RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset) describing the bucket that refused. Successful responses do not carry them yet. The public discovery documents and health probes have no rate limit, so they carry none.","contact":{"name":"PMM Tools","url":"https://www.pmmtools.com/docs/mcp"}},"externalDocs":{"description":"MCP quickstart","url":"https://www.pmmtools.com/docs/mcp"},"servers":[{"url":"https://www.pmmtools.com","description":"Production"}],"tags":[{"name":"Competitors","description":"Competitors and their scope."},{"name":"URLs","description":"Each competitor's URL pool."},{"name":"Captures","description":"Captured pages and capture uploads."},{"name":"Runs","description":"Starting and stopping capture runs."},{"name":"Discovery","description":"Site discovery results."},{"name":"Profile","description":"AI-written competitor profiles."},{"name":"News","description":"Competitor news and press mentions."},{"name":"Sync","description":"CLI downloads of audits."},{"name":"MCP","description":"The MCP server."},{"name":"Health","description":"Service probes."}],"paths":{"/api/competitors":{"get":{"operationId":"listCompetitors","summary":"List competitors","description":"List the caller's active (not deleted) competitors, most recently updated first.","tags":["Competitors"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}}],"responses":{"200":{"description":"The competitors.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Competitor list.","properties":{"competitors":{"type":"array","items":{"$ref":"#/components/schemas/Competitor"},"description":"Active competitors."}},"required":["competitors"],"additionalProperties":true}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"post":{"operationId":"createCompetitor","summary":"Create a competitor","description":"Create a competitor by primary domain, or return the existing one for that domain. 201 when created, 200 when it already existed. Counts against the plan competitor cap (402 when full).","tags":["Competitors"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}}],"requestBody":{"description":"The competitor to create.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"Create-competitor request.","properties":{"primary_domain":{"type":"string","description":"The competitor primary domain or a URL on it, e.g. \"example.com\".","minLength":1},"name":{"type":["string","null"],"description":"Display name; defaults to the domain.","minLength":1,"maxLength":200},"domains":{"type":"array","items":{"type":"string","description":"A domain alias."},"description":"Extra domains the competitor owns. primary_domain is always included.","maxItems":50}},"required":["primary_domain"],"additionalProperties":false}}}},"responses":{"200":{"description":"The competitor already existed.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Existing competitor.","properties":{"competitor":{"$ref":"#/components/schemas/Competitor"},"created":{"type":"boolean","description":"False: it already existed."}},"required":["competitor","created"],"additionalProperties":true}}}},"201":{"description":"The competitor was created.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"New competitor.","properties":{"competitor":{"$ref":"#/components/schemas/Competitor"},"created":{"type":"boolean","description":"True: it was created."}},"required":["competitor","created"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"The plan does not allow this, or its quota is used up.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"A dependency is unavailable or the feature is paused. Retry later.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}":{"get":{"operationId":"getCompetitor","summary":"Get a competitor","description":"Get one competitor with its URL counts.","tags":["Competitors"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The competitor.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Competitor detail.","properties":{"competitor":{"$ref":"#/components/schemas/CompetitorDetail"}},"required":["competitor"],"additionalProperties":true}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"patch":{"operationId":"updateCompetitorScope","summary":"Update competitor scope","description":"Set what the competitor is tracked for, its feature scope and its size class. Send at least one field; null clears it.","tags":["Competitors"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"description":"Fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"Scope update.","properties":{"tracked_for":{"type":["string","null"],"description":"Why this competitor is tracked.","maxLength":2000},"feature_scope":{"type":["array","null"],"items":{"type":"string","enum":["bot_detection","human_fraud","ai_control_governance"]},"description":"Feature dimensions the competitor competes on."},"size_class":{"type":["string","null"],"enum":["pure_play","platform_with_addons",null],"description":"Competitor size class."}},"additionalProperties":false,"minProperties":1}}}},"responses":{"200":{"description":"The updated scope.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Updated competitor scope.","properties":{"competitor":{"type":"object","description":"The changed fields.","properties":{"id":{"type":"string","format":"uuid","description":"Competitor id."},"tracked_for":{"type":["string","null"],"description":"Why it is tracked."},"feature_scope":{"type":["array","null"],"items":{"type":"string"},"description":"Feature dimensions."},"size_class":{"type":["string","null"],"description":"Size class."},"updated_at":{"type":"string","format":"date-time","description":"When it changed."}},"additionalProperties":true}},"required":["competitor"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"operationId":"deleteCompetitor","summary":"Delete a competitor","description":"Soft-delete a competitor. Send its domain as confirm_domain to confirm. It can be restored with restoreCompetitor until purge_after.","tags":["Competitors"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"description":"Delete confirmation.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"Delete confirmation.","properties":{"confirm_domain":{"type":"string","description":"The competitor's primary domain, to confirm.","minLength":1,"maxLength":253}},"required":["confirm_domain"],"additionalProperties":true}}}},"responses":{"200":{"description":"The competitor was deleted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Delete result.","properties":{"deleted":{"type":"boolean","const":true,"description":"Always true."},"competitor_id":{"type":"string","format":"uuid","description":"The deleted competitor."},"deleted_at":{"type":"string","format":"date-time","description":"When it was deleted."},"purge_after":{"type":"string","format":"date-time","description":"When it will be purged for good."},"released_protected_slot":{"type":"boolean","description":"Whether a protected-site slot was freed."}},"required":["deleted","competitor_id","deleted_at","purge_after"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflicts with the current state (frozen competitor, run in flight, over a cap).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/restore":{"post":{"operationId":"restoreCompetitor","summary":"Restore a deleted competitor","description":"Restore a soft-deleted competitor before it is purged.","tags":["Competitors"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The competitor was restored.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Restore result.","additionalProperties":true}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflicts with the current state (frozen competitor, run in flight, over a cap).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"410":{"description":"Gone: past its archive window, so it can no longer be restored.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/active-set":{"post":{"operationId":"setActiveCompetitors","summary":"Choose active competitors","description":"When the account holds more competitors than its plan allows, choose which stay active; the rest are frozen. Not applicable to admin accounts.","tags":["Competitors"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}}],"requestBody":{"description":"The competitors to keep active.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"Active set.","properties":{"active":{"type":"array","items":{"type":"string","format":"uuid","description":"A competitor id."},"description":"Competitor ids to keep active.","maxItems":100}},"required":["active"],"additionalProperties":true}}}},"responses":{"200":{"description":"The new active set.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Active set result.","properties":{"active":{"type":"array","items":{"type":"string","format":"uuid","description":"Competitor id."},"description":"Active competitors."},"frozen":{"type":"array","items":{"type":"string","format":"uuid","description":"Competitor id."},"description":"Frozen competitors."},"cap":{"type":"integer","description":"The plan competitor cap."}},"required":["active","frozen","cap"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflicts with the current state (frozen competitor, run in flight, over a cap).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"A dependency is unavailable or the feature is paused. Retry later.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/protection":{"get":{"operationId":"getCompetitorProtection","summary":"Get protected-site status","description":"Whether this competitor uses one of the plan protected-site slots, and how many slots are in use.","tags":["Competitors"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Protection status.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Protection status.","additionalProperties":true}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"put":{"operationId":"protectCompetitor","summary":"Protect a competitor","description":"Give this competitor a protected-site slot (stronger capture for sites that block bots). When every slot is taken, name the competitor to swap out.","tags":["Competitors"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"description":"Optional swap. An empty body is accepted.","required":false,"content":{"application/json":{"schema":{"type":"object","description":"Protect request.","properties":{"swap_out_id":{"type":["string","null"],"format":"uuid","description":"Competitor to unprotect in exchange."}},"additionalProperties":false}}}},"responses":{"200":{"description":"Protection result.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Protection result.","additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"The plan does not allow this, or its quota is used up.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"operationId":"unprotectCompetitor","summary":"Unprotect a competitor","description":"Release this competitor's protected-site slot.","tags":["Competitors"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Protection result.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Protection result.","additionalProperties":true}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/logo":{"get":{"operationId":"getCompetitorLogo","summary":"Get a competitor logo","description":"The competitor's logo image. 404 with a JSON error body when no logo was found.","tags":["Competitors"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The logo image.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"image/*":{"schema":{"type":"string","format":"binary","description":"Image bytes."}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/profile":{"get":{"operationId":"getCompetitorProfile","summary":"Get the competitor profile","description":"The latest AI-written competitor profile (null when none exists yet) and the state of profile generation.","tags":["Profile"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The profile.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Profile read result.","properties":{"profile":{"type":["object","null"],"additionalProperties":true,"description":"The latest successful profile, with citations."},"generation":{"type":"object","description":"Whether a profile is being generated, when it can be regenerated, and whether generation is on.","additionalProperties":true}},"required":["profile","generation"],"additionalProperties":true}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/profile/regenerate":{"post":{"operationId":"regenerateCompetitorProfile","summary":"Regenerate the competitor profile","description":"Start a new AI profile for the competitor from its captured pages. Once every 24 hours per competitor; spends AI quota. Poll getCompetitorProfile for the result.","tags":["Profile"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"responses":{"202":{"description":"Generation queued.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Queued profile generation.","properties":{"ai_worker_id":{"type":"string","format":"uuid","description":"The generation job."},"batch_id":{"type":"string","format":"uuid","description":"The run the profile is built from."},"model":{"type":"string","description":"The model that writes it."}},"required":["ai_worker_id","batch_id","model"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"The plan does not allow this, or its quota is used up.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflicts with the current state (frozen competitor, run in flight, over a cap).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"A dependency is unavailable or the feature is paused. Retry later.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/discovery-runs":{"get":{"operationId":"listDiscoveryRuns","summary":"List discovery runs","description":"List a competitor's site-discovery runs (navigation and footer link sets, sitemap counts), newest first.","tags":["Discovery"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}},{"name":"page","in":"query","required":false,"description":"Page number, starting at 1.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"description":"Runs per page (max 100).","schema":{"type":"integer","minimum":1,"maximum":100,"default":20}}],"responses":{"200":{"description":"A page of runs.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Discovery run page.","properties":{"competitor_id":{"type":"string","format":"uuid","description":"The competitor."},"runs":{"type":"array","items":{"type":"object","description":"A discovery run row.","additionalProperties":true},"description":"Runs on this page."},"pagination":{"$ref":"#/components/schemas/Pagination"}},"required":["competitor_id","runs","pagination"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"post":{"operationId":"createDiscoveryRun","summary":"Record a discovery run","description":"Store what a discovery pass found on the competitor's site: feature scope, size class, navigation and footer links and optional screenshots.","tags":["Discovery"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"description":"The discovery result.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"Discovery run.","properties":{"feature_scope":{"type":"array","items":{"type":"string","enum":["bot_detection","human_fraud","ai_control_governance"]},"description":"Feature dimensions found.","minItems":1},"competitor_size_class":{"type":"string","enum":["pure_play","platform_with_addons"],"description":"Size class found."},"feature_scope_version":{"type":"string","description":"Version of the feature-scope rubric used.","minLength":1},"nav_screenshot_base64":{"type":["string","null"],"description":"Base64 image of the site navigation."},"footer_screenshot_base64":{"type":["string","null"],"description":"Base64 image of the site footer."},"nav_links":{"type":"array","items":{"type":"object","description":"A link.","properties":{"href":{"type":"string","description":"Link target."},"text":{"type":"string","description":"Link text."}},"additionalProperties":true},"description":"Navigation links."},"footer_links":{"type":"array","items":{"type":"object","description":"A link.","properties":{"href":{"type":"string","description":"Link target."},"text":{"type":"string","description":"Link text."}},"additionalProperties":true},"description":"Footer links."},"sitemap_urls_count":{"type":"integer","description":"URLs found in the sitemap.","minimum":0},"candidate_urls_count":{"type":"integer","description":"Candidate URLs found.","minimum":0},"notes":{"type":["string","null"],"description":"Free-form notes.","maxLength":4000}},"required":["feature_scope","competitor_size_class"],"additionalProperties":true}}}},"responses":{"201":{"description":"The run was stored.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Stored discovery run.","properties":{"run_id":{"type":"string","format":"uuid","description":"The new run."},"competitor_id":{"type":"string","format":"uuid","description":"The competitor."},"discovered_at":{"type":"string","format":"date-time","description":"When it was stored."},"nav_screenshot_path":{"type":["string","null"],"description":"Storage path of the navigation screenshot."},"footer_screenshot_path":{"type":["string","null"],"description":"Storage path of the footer screenshot."}},"required":["run_id","competitor_id","discovered_at"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"A dependency is unavailable or the feature is paused. Retry later.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/canonical-snapshots":{"get":{"operationId":"listCanonicalSnapshots","summary":"List canonical snapshots","description":"For every captured page of the competitor, the snapshot that currently stands for it (markdown and screenshot pointers), up to 1000 pages.","tags":["Captures"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Canonical snapshots.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Canonical snapshot list.","properties":{"competitor_id":{"type":"string","format":"uuid","description":"The competitor."},"freshness_cutoff_days":{"type":"integer","description":"Days after which a snapshot counts as stale."},"items":{"type":"array","items":{"type":"object","description":"One page.","properties":{"competitor_url_id":{"type":"string","format":"uuid","description":"The page."},"url":{"type":"string","description":"Page URL."},"status":{"type":"string","description":"Freshness of the canonical snapshot."},"canonical_snapshot":{"type":["object","null"],"additionalProperties":true,"description":"The snapshot standing for this page."}},"additionalProperties":true},"description":"Captured pages."},"truncated":{"type":"boolean","description":"True when more captured pages exist than items holds."},"total_captured":{"type":"integer","description":"Exact number of captured pages."},"shown":{"type":"integer","description":"Number of items returned."}},"required":["competitor_id","items","truncated","total_captured","shown"],"additionalProperties":true}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/capture-coverage":{"get":{"operationId":"getCaptureCoverage","summary":"Get capture coverage","description":"How much of the competitor's URL pool is captured, and why the rest is not (blocked, queued, failed, excluded...).","tags":["Captures"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Coverage report.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Coverage report.","properties":{"competitor_id":{"type":"string","format":"uuid","description":"The competitor."},"last_run_at":{"type":["string","null"],"format":"date-time","description":"When the newest finished run started."},"total_urls":{"type":"integer","description":"URLs in the pool."},"captured":{"type":"integer","description":"Captured pages."}},"required":["competitor_id","total_urls","captured"],"additionalProperties":true}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/runs/{batchId}/cancel":{"post":{"operationId":"cancelCompetitorRun","summary":"Cancel a run","description":"Cancel a capture run of this competitor. Idempotent: a run that already finished answers success with already=true.","tags":["Runs"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}},{"name":"batchId","in":"path","required":true,"description":"The run (batch) id (UUID), as returned when the run was started.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The run is cancelled (or had already finished).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Cancel result.","properties":{"success":{"type":"boolean","description":"True when the run is stopped."},"already":{"type":"boolean","description":"True when it had already finished."}},"additionalProperties":true}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/urls":{"get":{"operationId":"listCompetitorUrls","summary":"List competitor URLs","description":"List the competitor's URL pool with capture state, filterable by status, category, source, freshness, search text and not-captured reason.","tags":["URLs"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}},{"name":"page","in":"query","required":false,"description":"Page number, starting at 1.","schema":{"type":"integer","minimum":1,"default":1}},{"name":"limit","in":"query","required":false,"description":"URLs per page (max 200).","schema":{"type":"integer","minimum":1,"maximum":200,"default":50}},{"name":"status","in":"query","required":false,"description":"Only URLs with this status.","schema":{"type":"string","enum":["included","excluded","auto"]}},{"name":"category","in":"query","required":false,"description":"Only URLs in this category slug.","schema":{"type":"string"}},{"name":"sources","in":"query","required":false,"description":"Comma-separated sources to include: crawler, chrome-ext, mcp, manual, discovery.","schema":{"type":"string"}},{"name":"freshnessMaxAgeDays","in":"query","required":false,"description":"Only URLs captured within this many days.","schema":{"type":"integer","minimum":1,"maximum":3650}},{"name":"search","in":"query","required":false,"description":"Substring to match in the URL.","schema":{"type":"string","maxLength":200}},{"name":"reason","in":"query","required":false,"description":"Only uncaptured URLs with this not-captured reason.","schema":{"type":"string","enum":["denied-by-policy","excluded-by-user","excluded-by-rule","escalating","queued","blocked","budget-exhausted","capture-failed","capture-rejected","not-selected-in-last-run","never-judged"]}},{"name":"include","in":"query","required":false,"description":"Comma-separated extras: latest_snapshot, capture_state.","schema":{"type":"string"}}],"responses":{"200":{"description":"A page of URLs.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"URL page.","properties":{"urls":{"type":"array","items":{"$ref":"#/components/schemas/CompetitorUrl"},"description":"URLs on this page."},"pagination":{"$ref":"#/components/schemas/Pagination"},"annotations_unavailable":{"type":"boolean","description":"True when the not-captured reasons could not be computed."},"capture_state_truncated":{"type":"boolean","description":"True when some capture states may be missing."}},"required":["urls","pagination"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/urls/{urlId}":{"get":{"operationId":"getCompetitorUrl","summary":"Get a competitor URL","description":"Get one URL of the competitor.","tags":["URLs"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}},{"name":"urlId","in":"path","required":true,"description":"The competitor URL id (UUID), as returned by listCompetitorUrls.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"The URL.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"URL.","properties":{"url":{"$ref":"#/components/schemas/CompetitorUrl"}},"required":["url"],"additionalProperties":true}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"patch":{"operationId":"setCompetitorUrlStatus","summary":"Set a URL status","description":"Include, exclude or reset (auto) one URL. Including a URL counts against the plan URL pool.","tags":["URLs"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}},{"name":"urlId","in":"path","required":true,"description":"The competitor URL id (UUID), as returned by listCompetitorUrls.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"description":"The new status.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"Status change.","properties":{"status":{"type":"string","enum":["included","excluded","auto"],"description":"The new status."}},"required":["status"],"additionalProperties":true}}}},"responses":{"200":{"description":"The updated URL.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"URL.","properties":{"url":{"$ref":"#/components/schemas/CompetitorUrl"}},"required":["url"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"The plan does not allow this, or its quota is used up.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflicts with the current state (frozen competitor, run in flight, over a cap).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/urls/{urlId}/snapshots":{"get":{"operationId":"listUrlSnapshots","summary":"List snapshots of a URL","description":"Capture history of one URL, newest first, with short-lived signed screenshot links.","tags":["Captures"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}},{"name":"urlId","in":"path","required":true,"description":"The competitor URL id (UUID), as returned by listCompetitorUrls.","schema":{"type":"string","format":"uuid"}},{"name":"include","in":"query","required":false,"description":"A snapshot id (UUID) to include even if outside the default window.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Snapshots.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Snapshot history.","properties":{"snapshots":{"type":"array","items":{"type":"object","description":"A snapshot.","properties":{"id":{"type":"string","format":"uuid","description":"Snapshot id."},"screenshot_url":{"type":["string","null"],"description":"Signed screenshot URL."}},"additionalProperties":true},"description":"Snapshots, newest first."}},"required":["snapshots"],"additionalProperties":true}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/urls/bulk":{"post":{"operationId":"bulkCompetitorUrls","summary":"Bulk URL action","description":"Two modes. Set status: send status and urlIds to include, exclude or reset many URLs at once. Enumerate: send action (recapture or revalidate) with optional ids or filters to list the URLs that action would touch, oldest attempt first.","tags":["URLs"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"description":"Either {status, urlIds} or {action, ids?, filters?}.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"Bulk request.","properties":{"status":{"type":"string","enum":["included","excluded","auto"],"description":"Set-status mode: the new status."},"urlIds":{"type":"array","items":{"type":"string","format":"uuid","description":"URL id."},"description":"Set-status mode: the URLs to change.","minItems":1,"maxItems":500},"action":{"type":"string","enum":["recapture","revalidate"],"description":"Enumerate mode: the action to list URLs for."},"ids":{"type":"array","items":{"type":"string","format":"uuid","description":"URL id."},"description":"Enumerate mode: limit to these URLs.","minItems":1,"maxItems":500},"filters":{"type":"object","description":"Enumerate mode: filters.","properties":{"status":{"type":"string","enum":["included","excluded","auto"],"description":"Only this status."},"sources":{"type":"array","items":{"type":"string","enum":["crawler","chrome-ext","mcp","manual","discovery"]},"description":"Only these sources."},"freshnessMaxAgeDays":{"type":"integer","description":"Only URLs captured within this many days.","minimum":1,"maximum":3650}},"additionalProperties":false}},"additionalProperties":true}}}},"responses":{"200":{"description":"Set-status result ({rowsAffected, requested, status}) or enumeration ({action, competitor_id, count, urls}).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Bulk result.","properties":{"rowsAffected":{"type":"integer","description":"Set-status mode: rows changed."},"requested":{"type":"integer","description":"Set-status mode: ids sent."},"status":{"type":"string","description":"Set-status mode: the status applied."},"action":{"type":"string","description":"Enumerate mode: the action."},"competitor_id":{"type":"string","format":"uuid","description":"Enumerate mode: the competitor."},"count":{"type":"integer","description":"Enumerate mode: URLs listed."},"urls":{"type":"array","items":{"type":"object","description":"A URL row.","additionalProperties":true},"description":"Enumerate mode: the URLs."}},"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"The plan does not allow this, or its quota is used up.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflicts with the current state (frozen competitor, run in flight, over a cap).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/urls/import":{"post":{"operationId":"importCompetitorUrls","summary":"Import URLs","description":"Add URLs to the competitor pool, as a newline-separated string or an array. Invalid lines are returned in skipped.","tags":["URLs"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"description":"URLs to import.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"Import request.","properties":{"urls":{"oneOf":[{"type":"string","description":"Newline-separated URLs."},{"type":"array","items":{"type":"string","description":"A URL."},"description":"URLs."}],"description":"URLs to import (max 500 valid URLs)."},"status":{"type":"string","enum":["included","excluded","auto"],"default":"included","description":"Status for the imported URLs."}},"required":["urls"],"additionalProperties":true}}}},"responses":{"200":{"description":"Import result.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Import result.","properties":{"rowsAffected":{"type":"integer","description":"Rows written."},"imported":{"type":"integer","description":"New URLs."},"statusApplied":{"type":"integer","description":"Existing URLs whose status changed."},"requested":{"type":"integer","description":"Valid URLs sent."},"skipped":{"type":"array","items":{"type":"object","description":"An invalid line.","properties":{"raw":{"type":"string","description":"The line."},"reason":{"type":"string","description":"Why it was skipped."}},"additionalProperties":true},"description":"Invalid lines."}},"required":["rowsAffected","requested","skipped"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"The plan does not allow this, or its quota is used up.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflicts with the current state (frozen competitor, run in flight, over a cap).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/collections/urls":{"post":{"operationId":"addCollectionUrls","summary":"Add URLs to collections","description":"Add URLs from any site; each is filed under the competitor that owns its domain (optionally creating competitors). Each URL may carry an explicit competitor_id and a sync_id for the caller to match results.","tags":["URLs"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}}],"requestBody":{"description":"URLs to add.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"Collection add request.","properties":{"urls":{"oneOf":[{"type":"string","description":"Newline-separated URLs."},{"type":"array","items":{"oneOf":[{"type":"string","description":"A URL."},{"type":"object","description":"A URL with options.","properties":{"url":{"type":"string","description":"The URL."},"competitor_id":{"type":"string","format":"uuid","description":"File it under this competitor."},"sync_id":{"type":"string","description":"Caller id to match the result.","minLength":1,"maxLength":128}},"required":["url"],"additionalProperties":true}]},"description":"URLs."}],"description":"URLs to add (max 500 valid URLs)."},"status":{"type":"string","enum":["included","excluded","auto"],"description":"Status for the added URLs."},"source":{"type":"string","enum":["crawler","chrome-ext","mcp","manual","discovery"],"description":"Where the URLs came from."},"auto_create_competitors":{"type":"boolean","description":"Create a competitor for a domain that has none."},"competitor_id":{"type":"string","format":"uuid","description":"File every URL under this competitor."}},"required":["urls"],"additionalProperties":true}}}},"responses":{"200":{"description":"Per-URL results.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Collection add result.","properties":{"summary":{"type":"object","description":"Counts.","properties":{"requested":{"type":"integer","description":"URLs sent."},"created":{"type":"integer","description":"New URLs."},"existed":{"type":"integer","description":"Already tracked."},"rejected":{"type":"integer","description":"Refused."}},"required":["requested","created","existed","rejected"],"additionalProperties":true},"results":{"type":"array","items":{"type":"object","description":"One URL outcome.","additionalProperties":true},"description":"Per-URL outcomes."}},"required":["summary","results"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"The plan does not allow this, or its quota is used up.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"operationId":"listCollectionUrlUpdates","summary":"List submitted URL updates","description":"URLs added with a sync_id that have since been submitted for capture, oldest first. Pass the returned cursor as since to page.","tags":["URLs"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"since","in":"query","required":false,"description":"Only updates after this ISO-8601 timestamp (the previous cursor).","schema":{"type":"string","format":"date-time"}},{"name":"limit","in":"query","required":false,"description":"Results per call (max 500).","schema":{"type":"integer","minimum":1,"maximum":500,"default":100}}],"responses":{"200":{"description":"Updates.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Update page.","properties":{"results":{"type":"array","items":{"type":"object","description":"One update.","properties":{"sync_id":{"type":"string","description":"The caller sync id."},"submitted_batch_id":{"type":"string","format":"uuid","description":"The capture run."},"submitted_at":{"type":"string","format":"date-time","description":"When it was submitted."}},"required":["sync_id","submitted_batch_id","submitted_at"],"additionalProperties":true},"description":"Updates."},"cursor":{"type":["string","null"],"format":"date-time","description":"Pass as since for the next page; null when empty."}},"required":["results","cursor"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitor-urls/{id}/validation":{"patch":{"operationId":"validateCompetitorUrl","summary":"Mark a URL useful or not","description":"Record whether a URL is useful. Useful includes it; not useful excludes it and stores the reason.","tags":["URLs"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor URL id (UUID).","schema":{"type":"string","format":"uuid"}}],"requestBody":{"description":"The verdict.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"Validation verdict.","properties":{"is_useful":{"type":"boolean","description":"Whether the page is useful."},"reason":{"type":"string","description":"Why. Required on every request; must be non-empty when is_useful is false.","maxLength":2000}},"required":["is_useful","reason"],"additionalProperties":true}}}},"responses":{"200":{"description":"The new state.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Validation result.","properties":{"competitor_url_id":{"type":"string","format":"uuid","description":"The URL."},"status":{"type":"string","enum":["included","excluded","auto"],"description":"Its status now."},"validation_reason":{"type":["string","null"],"description":"The stored reason."}},"required":["competitor_url_id","status","validation_reason"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"The plan does not allow this, or its quota is used up.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflicts with the current state (frozen competitor, run in flight, over a cap).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitor-urls/{id}/capture":{"post":{"operationId":"writeCompetitorUrlCapture","summary":"Upload a capture of a URL","description":"Store a capture the caller made of a tracked URL (markdown and HTML), or record why it could not be captured. Send this request directly to the core host: bodies can exceed 4.5 MB.","tags":["Captures"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor URL id (UUID).","schema":{"type":"string","format":"uuid"}}],"requestBody":{"description":"The capture.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"Capture upload.","properties":{"outcome":{"type":"string","enum":["captured","redirected","not_found","js_only","auth_required","paywall","network_error","skipped"],"description":"What happened. captured requires markdown and html."},"markdown":{"type":"string","description":"Page markdown."},"html":{"type":"string","description":"Page HTML (max 10,000,000 bytes)."},"meta_title":{"type":"string","description":"Page title."},"meta_description":{"type":"string","description":"Page meta description."}},"required":["outcome"],"additionalProperties":true}}}},"responses":{"200":{"description":"The capture was recorded.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Capture result.","properties":{"competitor_url_id":{"type":"string","format":"uuid","description":"The URL."},"outcome":{"type":"string","description":"The recorded outcome."},"snapshot_id":{"type":["string","null"],"format":"uuid","description":"The new snapshot, when one was stored."},"sources":{"type":"array","items":{"type":"string","description":"A source."},"description":"URL sources."},"latest_good_snapshot_at":{"type":["string","null"],"format":"date-time","description":"Newest good snapshot."},"capture_acceptance":{"type":["string","null"],"description":"Acceptance verdict."},"capture_rejection_reason":{"type":["string","null"],"description":"Why the capture was not accepted."},"promoted":{"type":"boolean","description":"Whether it became the canonical snapshot."}},"required":["competitor_url_id","outcome"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"The body is too large.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/extension/captures":{"post":{"operationId":"createExtensionCapture","summary":"Upload a page capture","description":"Store a capture of any page (HTML, optional JPEG screenshot and metadata), filed under the competitor for its domain. Used by the Chrome extension; send directly to the core host (bodies up to 24 MB).","tags":["Captures"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}}],"requestBody":{"description":"The capture.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"Page capture.","properties":{"url":{"type":"string","description":"The page URL (http or https)."},"html":{"type":"string","description":"Page HTML (max 10,000,000 bytes).","minLength":1},"metadata":{"type":"object","description":"Page metadata.","properties":{"title":{"type":["string","null"],"description":"Page title."},"description":{"type":["string","null"],"description":"Meta description."}},"additionalProperties":true},"screenshot":{"type":"object","description":"Screenshot.","properties":{"dataUrl":{"type":"string","description":"A data:image/jpeg;base64, URL (max 8,000,000 bytes decoded)."}},"required":["dataUrl"],"additionalProperties":true}},"required":["url","html"],"additionalProperties":true}}}},"responses":{"200":{"description":"The capture was stored.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Extension capture result.","properties":{"ok":{"type":"boolean","const":true,"description":"Always true."},"outcome":{"type":"string","description":"What was stored."},"snapshot_id":{"type":["string","null"],"format":"uuid","description":"The new snapshot."},"competitor":{"type":"object","description":"The competitor it was filed under.","additionalProperties":true},"competitor_url_id":{"type":"string","format":"uuid","description":"The URL row."},"competitor_url_status":{"type":"string","description":"Its status."},"web_url":{"type":"string","description":"Link to the competitor in the app.","format":"uri"}},"required":["ok","outcome","competitor","competitor_url_id","web_url"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"The plan does not allow this, or its quota is used up.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"413":{"description":"The body is too large.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/submit-urls":{"post":{"operationId":"submitUrls","summary":"Capture URLs now","description":"Start capture runs for up to 50 URLs, grouped by competitor (created when needed). URLs captured recently are skipped unless force is true. Spends capture quota.","tags":["Runs"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"force","in":"query","required":false,"description":"Same as the body force flag, when the body omits it.","schema":{"type":"string"}}],"requestBody":{"description":"URLs to capture.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"Submit request.","properties":{"urls":{"type":"array","items":{"type":"string","description":"A URL."},"description":"URLs to capture.","minItems":1},"force":{"type":"boolean","description":"Recapture URLs even when a fresh snapshot exists."}},"required":["urls"],"additionalProperties":true}}}},"responses":{"200":{"description":"Runs started.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Submit result.","properties":{"success":{"type":"boolean","const":true,"description":"Always true on 200."},"batchId":{"type":["string","null"],"format":"uuid","description":"The first run's id."},"queued":{"type":"array","items":{"type":"string","description":"A URL."},"description":"URLs queued for capture."},"skipped":{"type":"array","items":{"type":"object","description":"A skipped URL and why.","additionalProperties":true},"description":"Fresh URLs not recaptured."},"exceededCap":{"type":"integer","description":"URLs beyond the per-submission cap."},"results":{"type":"array","items":{"type":"object","description":"Per-URL result.","additionalProperties":true},"description":"Per-URL results."},"runs":{"type":"array","items":{"type":"object","description":"A started run.","additionalProperties":true},"description":"Runs started, one per competitor."},"unresolved":{"type":"array","items":{"type":"object","description":"A URL that got no run.","properties":{"url":{"type":"string","description":"The URL."},"reason":{"type":"string","description":"Why."}},"additionalProperties":true},"description":"URLs that got no run."}},"required":["success","batchId","queued","skipped","results","runs","unresolved"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"The plan does not allow this, or its quota is used up.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"503":{"description":"A dependency is unavailable or the feature is paused. Retry later.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/submit-urls/preview":{"post":{"operationId":"previewSubmitUrls","summary":"Preview a capture","description":"Dry run of submitUrls: which competitor each URL would go to, which would be skipped as fresh, and what exceeds the cap. Writes nothing.","tags":["Runs"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"force","in":"query","required":false,"description":"Same as the body force flag, when the body omits it.","schema":{"type":"string"}}],"requestBody":{"description":"URLs to preview.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"Preview request.","properties":{"urls":{"type":"array","items":{"type":"string","description":"A URL."},"description":"URLs to preview.","minItems":1},"force":{"type":"boolean","description":"Preview as if forcing recapture."}},"required":["urls"],"additionalProperties":true}}}},"responses":{"200":{"description":"What a submit would do.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Submit preview.","properties":{"success":{"type":"boolean","const":true,"description":"Always true on 200."},"dryRun":{"type":"boolean","const":true,"description":"Always true."},"groups":{"type":"array","items":{"type":"object","description":"URLs grouped by the competitor that would get the run.","additionalProperties":true},"description":"Groups."},"skipped":{"type":"array","items":{"type":"object","description":"A fresh URL that would be skipped.","additionalProperties":true},"description":"Skipped URLs."},"exceededCap":{"type":"integer","description":"URLs beyond the per-submission cap."},"maxCompetitorsPerSubmission":{"type":"integer","description":"Runs a submit starts at most."}},"required":["success","dryRun","groups","skipped","exceededCap","maxCompetitorsPerSubmission"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"The plan does not allow this, or its quota is used up.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/snapshot-run/preview":{"post":{"operationId":"previewSnapshotRun","summary":"Preview a pasted URL list","description":"Parse pasted text into URLs, group them by registrable domain, match each group to an existing competitor and count fresh pages. Read-only.","tags":["Runs"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}}],"requestBody":{"description":"The pasted text.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"Paste preview request.","properties":{"text":{"type":"string","description":"Pasted URLs, one per line."}},"required":["text"],"additionalProperties":true}}}},"responses":{"200":{"description":"The review step.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Paste preview.","properties":{"groups":{"type":"array","items":{"type":"object","description":"URLs for one registrable domain and its matched competitor.","additionalProperties":true},"description":"Groups."},"invalid":{"type":"array","items":{"type":"object","description":"An invalid line.","properties":{"raw":{"type":"string","description":"The line."},"reason":{"type":"string","description":"Why."}},"additionalProperties":true},"description":"Invalid lines."},"totalValid":{"type":"integer","description":"Valid URLs."}},"required":["groups","invalid","totalValid"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/snapshot-run/commit":{"post":{"operationId":"commitSnapshotRun","summary":"Commit a pasted URL list","description":"Commit the groups from previewSnapshotRun: file each group under an existing competitor, create one, or exclude it. mode=snapshot also starts capture for up to 50 URLs; mode=add only adds them.","tags":["Runs"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}}],"requestBody":{"description":"The groups to commit.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"Commit request.","properties":{"mode":{"type":"string","enum":["snapshot","add"],"description":"snapshot captures now; add only adds the URLs."},"force":{"type":"boolean","description":"Recapture fresh URLs too (mode=snapshot)."},"groups":{"type":"array","items":{"type":"object","description":"One domain group.","properties":{"registrableDomain":{"type":"string","description":"The registrable domain.","minLength":1},"urls":{"type":"array","items":{"type":"string","description":"A URL."},"description":"URLs in the group."},"target":{"type":"object","description":"Where the group goes. kind=existing needs competitorId; kind=create takes an optional name; kind=exclude drops it.","properties":{"kind":{"type":"string","enum":["existing","create","exclude"],"description":"Target kind."},"competitorId":{"type":"string","description":"kind=existing: the competitor id."},"name":{"type":"string","description":"kind=create: the new competitor name."}},"required":["kind"],"additionalProperties":true}},"required":["registrableDomain","urls","target"],"additionalProperties":true},"description":"Groups to commit.","minItems":1}},"required":["mode","groups"],"additionalProperties":true}}}},"responses":{"200":{"description":"Commit result.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Commit result.","properties":{"mode":{"type":"string","enum":["snapshot","add"],"description":"The mode used."},"groups":{"type":"array","items":{"type":"object","description":"A committed group.","additionalProperties":true},"description":"Committed groups."},"summary":{"type":"object","description":"Counts.","additionalProperties":true},"perUrl":{"type":"array","items":{"type":"object","description":"One URL outcome.","additionalProperties":true},"description":"Per-URL outcomes."},"capture":{"type":["object","null"],"additionalProperties":true,"description":"The capture started (mode=snapshot), or null."}},"required":["mode","groups","summary","perUrl","capture"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"The plan does not allow this, or its quota is used up.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/news/sources":{"get":{"operationId":"listNewsSources","summary":"List news sources","description":"List the feeds watched for the competitor's own news. 404 when news tracking is not enabled for the account.","tags":["News"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"News sources.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"News sources.","additionalProperties":true}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"put":{"operationId":"setNewsSource","summary":"Add or remove a news source","description":"Add or remove one feed URL watched for the competitor.","tags":["News"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"description":"The change.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"News source change.","properties":{"action":{"type":"string","enum":["add","remove"],"description":"Add or remove."},"url":{"type":"string","description":"The feed URL.","minLength":1,"maxLength":2048}},"required":["action","url"],"additionalProperties":true}}}},"responses":{"200":{"description":"The new source list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"News sources.","additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"The plan does not allow this, or its quota is used up.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflicts with the current state (frozen competitor, run in flight, over a cap).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"The request is well-formed but cannot be processed (for example, the URL is not a feed).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/news/suggest":{"post":{"operationId":"suggestNewsSources","summary":"Suggest news sources","description":"Look for feeds on the competitor's site that could be watched.","tags":["News"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Suggested feeds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Suggested feeds.","additionalProperties":true}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"The plan does not allow this, or its quota is used up.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflicts with the current state (frozen competitor, run in flight, over a cap).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"The request is well-formed but cannot be processed (for example, the URL is not a feed).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/news/refresh":{"post":{"operationId":"refreshNews","summary":"Check news now","description":"Check the competitor's watched feeds now.","tags":["News"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"responses":{"200":{"description":"Check result.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"News check result.","additionalProperties":true}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"The plan does not allow this, or its quota is used up.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflicts with the current state (frozen competitor, run in flight, over a cap).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"The request is well-formed but cannot be processed (for example, the URL is not a feed).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/news/mentions/refresh":{"post":{"operationId":"refreshPressMentions","summary":"Check the press now","description":"Queue a check of the press for mentions of the competitor. Paid plans with scheduled tracking; once a day per competitor; spends AI quota.","tags":["News"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"responses":{"202":{"description":"The check was queued.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Queued.","properties":{"queued":{"type":"boolean","const":true,"description":"Always true."}},"required":["queued"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"The plan does not allow this, or its quota is used up.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"409":{"description":"Conflicts with the current state (frozen competitor, run in flight, over a cap).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/competitors/{id}/news/mentions/profile":{"put":{"operationId":"setPressProfile","summary":"Change the press-search settings","description":"Change how the competitor's press is searched: on or off, the brand name, other names, context terms, exclude terms, and whether the name is ambiguous. Omitted fields are left as they are. Spends nothing.","tags":["News"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"id","in":"path","required":true,"description":"The competitor id (UUID), as returned by listCompetitors.","schema":{"type":"string","format":"uuid"}}],"requestBody":{"description":"The fields to change.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"Press-search settings.","properties":{"enabled":{"type":"boolean","description":"Whether the press is checked for this competitor."},"brand_name":{"type":"string","description":"The name the press uses.","minLength":1,"maxLength":120},"aliases":{"type":"array","maxItems":5,"items":{"type":"string","description":"Another name.","maxLength":120},"description":"Other names, at most 5."},"context_terms":{"type":"array","maxItems":5,"items":{"type":"string","description":"A context term.","maxLength":80},"description":"What the company does, at most 5."},"exclude_terms":{"type":"array","maxItems":10,"items":{"type":"string","description":"An exclude term.","maxLength":80},"description":"Stories containing these are dropped, at most 10."},"ambiguous":{"type":"boolean","description":"True skips Google News, which cannot tell same-named companies apart."}},"additionalProperties":true}}}},"responses":{"200":{"description":"The saved settings.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Press-search settings.","additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"402":{"description":"The plan does not allow this, or its quota is used up.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"422":{"description":"The request is well-formed but cannot be processed (for example, the URL is not a feed).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/sync/audits":{"get":{"operationId":"listSyncAudits","summary":"List audits to sync","description":"List the caller's 50 newest audits, optionally filtered by domain, for the CLI sync command.","tags":["Sync"],"security":[{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"domain","in":"query","required":false,"description":"Only audits whose domain contains this text.","schema":{"type":"string"}}],"responses":{"200":{"description":"Audits.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Audit list.","properties":{"audits":{"type":"array","items":{"type":"object","description":"An audit.","properties":{"id":{"type":"string","format":"uuid","description":"Audit id."},"domain":{"type":"string","description":"Audited domain."},"status":{"type":["string","null"],"description":"Audit status."},"workflowStatus":{"type":["string","null"],"description":"Workflow status."},"competitorAnalysisStatus":{"type":["string","null"],"description":"Competitor analysis phase status."},"createdAt":{"type":"string","format":"date-time","description":"Created."},"updatedAt":{"type":"string","format":"date-time","description":"Updated."}},"required":["id","domain"],"additionalProperties":true},"description":"Audits, newest first."}},"required":["audits"],"additionalProperties":true}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/sync/{auditId}":{"get":{"operationId":"getSyncManifest","summary":"Get an audit sync manifest","description":"Everything the CLI downloads for one audit: analysis, snapshots with signed markdown and screenshot links, and the bundle link.","tags":["Sync"],"security":[{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}},{"name":"auditId","in":"path","required":true,"description":"The audit id (UUID), as returned by listSyncAudits.","schema":{"type":"string","format":"uuid"}},{"name":"include","in":"query","required":false,"description":"Comma-separated parts: analysis, markdown, screenshots, bundle. Default all.","schema":{"type":"string"}},{"name":"since","in":"query","required":false,"description":"Only snapshots after this ISO-8601 timestamp.","schema":{"type":"string","format":"date-time"}}],"responses":{"200":{"description":"The manifest.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Sync manifest.","properties":{"auditId":{"type":"string","format":"uuid","description":"The audit."},"domain":{"type":"string","description":"Audited domain."},"syncedAt":{"type":"string","format":"date-time","description":"When the manifest was built."},"analysis":{"type":["object","null"],"additionalProperties":true,"description":"The analysis, when included."},"snapshots":{"type":"array","items":{"type":"object","description":"A snapshot with signed links.","additionalProperties":true},"description":"Snapshots."},"bundleSignedUrl":{"type":["string","null"],"description":"Signed bundle download link (part 1 when split)."},"bundleParts":{"type":"array","items":{"type":"object","description":"A bundle part with its signed link.","additionalProperties":true},"description":"Present when the bundle is split."},"totalSnapshots":{"type":"integer","description":"Snapshots in the audit."}},"required":["auditId","domain","syncedAt","snapshots","totalSnapshots"],"additionalProperties":true}}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"404":{"description":"Not found, or not owned by the caller.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/mcp":{"post":{"operationId":"callMcp","summary":"MCP JSON-RPC endpoint","description":"The PMM Tools MCP server (Streamable HTTP, stateless, JSON responses). POST one JSON-RPC 2.0 message: initialize, tools/list, tools/call and so on. Requests with a non-loopback Origin header are refused. See /docs/mcp for the tool list.","tags":["MCP"],"security":[{"apiKey":[]},{"bearerJwt":[]},{"openIdConnect":["openid","email"]}],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}}],"requestBody":{"description":"A JSON-RPC 2.0 request.","required":true,"content":{"application/json":{"schema":{"type":"object","description":"JSON-RPC request.","properties":{"jsonrpc":{"type":"string","const":"2.0","description":"Protocol version."},"id":{"type":["string","integer","null"],"description":"Request id; omit for a notification."},"method":{"type":"string","description":"MCP method, e.g. tools/call."},"params":{"type":"object","description":"Method parameters.","additionalProperties":true}},"required":["jsonrpc","method"],"additionalProperties":true}}}},"responses":{"200":{"description":"The JSON-RPC response.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"JSON-RPC response.","properties":{"jsonrpc":{"type":"string","const":"2.0","description":"Protocol version."},"id":{"type":["string","integer","null"],"description":"The request id."},"result":{"type":"object","description":"The method result.","additionalProperties":true},"error":{"type":"object","description":"JSON-RPC error.","properties":{"code":{"type":"integer","description":"Error code."},"message":{"type":"string","description":"Error message."}},"additionalProperties":true}},"required":["jsonrpc"],"additionalProperties":true}}}},"202":{"description":"A notification was accepted (no body).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}}},"400":{"description":"The request is invalid (bad id, body or parameter).","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"401":{"description":"No credential, or the credential was not accepted.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"403":{"description":"Refused: origin check, suspended account, team membership, or the site is on the capture deny list.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"429":{"description":"Rate limited. Wait the Retry-After seconds.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"},"RateLimit-Policy":{"$ref":"#/components/headers/RateLimitPolicy"},"RateLimit":{"$ref":"#/components/headers/RateLimit"},"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}},"500":{"description":"Server error.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"get":{"operationId":"getMcpMethodNotAllowed","summary":"MCP GET (not supported)","description":"The server is stateless and offers no SSE stream: GET always answers 405 with a JSON error. Use POST.","tags":["MCP"],"security":[],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}}],"responses":{"405":{"description":"Method not allowed.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}},"delete":{"operationId":"deleteMcpSession","summary":"MCP session delete (not supported)","description":"The server keeps no sessions: DELETE always answers 405 with a JSON error.","tags":["MCP"],"security":[],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}}],"responses":{"405":{"description":"Method not allowed.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/mcp/server-card":{"get":{"operationId":"getMcpServerCard","summary":"MCP server card","description":"The SEP-2127 server card: name, endpoint, transport, auth and tools of the MCP server. Public.","tags":["MCP"],"security":[],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}}],"responses":{"200":{"description":"The server card.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"SEP-2127 server card.","additionalProperties":true}}}}}}},"/api/health":{"get":{"operationId":"getHealth","summary":"Readiness check","description":"Readiness: healthy when the database answers; starting during the boot grace period. 503 when unhealthy or misconfigured. Public.","tags":["Health"],"security":[],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}}],"responses":{"200":{"description":"Healthy or starting.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Readiness.","properties":{"status":{"type":"string","enum":["healthy","starting","unhealthy","misconfigured"],"description":"Readiness state."},"supabase":{"type":"boolean","description":"Whether the database answered."},"timestamp":{"type":"string","format":"date-time","description":"When checked."}},"required":["status","timestamp"],"additionalProperties":true}}}},"503":{"description":"A dependency is unavailable or the feature is paused. Retry later.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}},"/api/health/live":{"get":{"operationId":"getLiveness","summary":"Liveness check","description":"Liveness: answers alive without touching any dependency. Public.","tags":["Health"],"security":[],"parameters":[{"name":"API-Version","in":"header","required":false,"description":"Optional. The API version the client expects. \"1\" is the only version; the server answers version 1 whatever this says.","schema":{"type":"string","enum":["1"],"default":"1"}}],"responses":{"200":{"description":"Alive.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"type":"object","description":"Liveness.","properties":{"status":{"type":"string","enum":["alive","misconfigured"],"description":"Liveness state."},"timestamp":{"type":"string","format":"date-time","description":"When checked."}},"required":["status","timestamp"],"additionalProperties":true}}}},"503":{"description":"A dependency is unavailable or the feature is paused. Retry later.","headers":{"API-Version":{"$ref":"#/components/headers/ApiVersion"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiError"}}}}}}}},"components":{"securitySchemes":{"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"pmm_live_...","description":"A PMM Tools connection key (API key), created in the app, sent as \"Authorization: Bearer pmm_live_...\"."},"bearerJwt":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"A Supabase Auth access token: an OAuth access token from the authorization server described at /.well-known/oauth-authorization-server, or a signed-in session token (as the Chrome extension and CLI send)."},"openIdConnect":{"type":"openIdConnect","openIdConnectUrl":"https://cmaehvhflgqdevabdlnp.supabase.co/auth/v1/.well-known/openid-configuration","description":"OAuth 2.1 authorization code with PKCE (S256) through Supabase Auth, the authorization server and OpenID provider. The access token it issues is sent as \"Authorization: Bearer <token>\"."}},"headers":{"ApiVersion":{"description":"The API version that answered. Always \"1\" today.","schema":{"type":"string","enum":["1"]}},"RetryAfter":{"description":"Seconds to wait before retrying (RFC 9110 §10.2.3).","schema":{"type":"integer","minimum":0}},"RateLimitPolicy":{"description":"The refusing bucket's quota and window in seconds (draft-ietf-httpapi-ratelimit-headers), e.g. \"default\";q=120;w=3600. Present on 429s from the shared limiters.","schema":{"type":"string"}},"RateLimit":{"description":"The refusing bucket's remaining requests and seconds until it resets (draft-ietf-httpapi-ratelimit-headers), e.g. \"default\";r=0;t=1800. Absent when the limiter store was unreachable (the count is unknown).","schema":{"type":"string"}},"RateLimitLimit":{"description":"Requests the refusing bucket allows per window (the earlier drafts' field). Present on 429s from the shared limiters.","schema":{"type":"integer","minimum":0}},"RateLimitRemaining":{"description":"Requests left in the current window (the earlier drafts' field). Absent when the count is unknown.","schema":{"type":"integer","minimum":0}},"RateLimitReset":{"description":"Seconds until the window resets (the earlier drafts' field). Absent when the count is unknown.","schema":{"type":"integer","minimum":0}}},"schemas":{"ApiError":{"type":"object","description":"A JSON error. error is the machine code existing clients compare; code repeats it; message and hint say what went wrong and what to do.","properties":{"error":{"type":"string","description":"Machine-readable error code (or, on older routes, a short sentence)."},"code":{"type":"string","description":"The error code."},"message":{"type":"string","description":"What went wrong."},"hint":{"type":"string","description":"What to do next."}},"required":["error"],"additionalProperties":true},"Pagination":{"type":"object","description":"Page position.","properties":{"page":{"type":"integer","description":"This page."},"limit":{"type":"integer","description":"Items per page."},"total":{"type":"integer","description":"Items across all pages."},"totalPages":{"type":"integer","description":"Number of pages."}},"required":["page","limit","total","totalPages"],"additionalProperties":true},"Competitor":{"type":"object","description":"A tracked competitor.","properties":{"id":{"type":"string","format":"uuid","description":"Competitor id."},"name":{"type":["string","null"],"description":"Display name."},"primary_domain":{"type":"string","description":"Primary domain."},"domains":{"type":"array","items":{"type":"string","description":"A domain."},"description":"All domains, primary included."},"tracked_for":{"type":["string","null"],"description":"Why it is tracked."},"feature_scope":{"type":["array","null"],"items":{"type":"string"},"description":"Feature dimensions."},"size_class":{"type":["string","null"],"description":"Size class."},"frozen_at":{"type":["string","null"],"format":"date-time","description":"When it was frozen (over the plan cap)."},"protected_at":{"type":["string","null"],"format":"date-time","description":"When it got a protected-site slot."},"snapshot_freshness_cutoff_days":{"type":["integer","null"],"description":"Days after which a snapshot is stale."},"created_at":{"type":"string","format":"date-time","description":"Created."},"updated_at":{"type":"string","format":"date-time","description":"Updated."}},"required":["id","primary_domain","domains","created_at","updated_at"],"additionalProperties":true},"CompetitorDetail":{"allOf":[{"$ref":"#/components/schemas/Competitor"},{"type":"object","description":"Competitor with counts.","properties":{"counts":{"type":"object","description":"URL and snapshot counts.","additionalProperties":true}},"required":["counts"],"additionalProperties":true}],"description":"A competitor with its counts."},"CompetitorUrl":{"type":"object","description":"A URL in a competitor's pool.","properties":{"id":{"type":"string","format":"uuid","description":"URL id."},"competitor_id":{"type":"string","format":"uuid","description":"The competitor."},"url":{"type":"string","description":"The URL."},"normalized_url":{"type":"string","description":"Normalized form used for matching."},"status":{"type":"string","enum":["included","excluded","auto"],"description":"included, excluded or auto."},"sources":{"type":"array","items":{"type":"string","enum":["crawler","chrome-ext","mcp","manual","discovery"]},"description":"Where the URL came from."},"category_slug":{"type":["string","null"],"description":"Page category."},"meta_title":{"type":["string","null"],"description":"Page title."},"meta_description":{"type":["string","null"],"description":"Meta description."},"latest_good_snapshot_id":{"type":["string","null"],"format":"uuid","description":"Newest good snapshot."},"latest_good_snapshot_at":{"type":["string","null"],"format":"date-time","description":"When it was taken."},"last_capture_attempt_at":{"type":["string","null"],"format":"date-time","description":"Last capture attempt."},"validation_reason":{"type":["string","null"],"description":"Why it was marked not useful."},"captured":{"type":"boolean","description":"Whether the page has a served capture."},"not_captured_reason":{"type":["string","null"],"description":"Why it is not captured, when known."},"created_at":{"type":"string","format":"date-time","description":"Created."},"updated_at":{"type":"string","format":"date-time","description":"Updated."}},"required":["id","competitor_id","url","status"],"additionalProperties":true}}}}