{
  "openapi": "3.1.0",
  "info": {
    "title": "Pathmote API",
    "version": "0.1.0",
    "summary": "Evidence-first discovery of advertised website entry points",
    "description": "Pathmote implements one bounded, read-only lookup for a public website URL. Results distinguish advertisement, reachability, format verification, coverage, and limitations. This build defaults to disabled lookups and disabled proposal intake (503). Enabled response contracts below apply only after the corresponding operator gates are satisfied; this description is not evidence of a hosted deployment."
  },
  "servers": [
    {
      "url": "/",
      "description": "The same origin that served this description"
    }
  ],
  "x-pathmote-capabilities": {
    "lookup": "read-only",
    "lookup_enabled": false,
    "proposals": "disabled",
    "remote_mcp": false,
    "live_target_policy": "controlled-origins-only-until-public-target-enforcement-is-verified"
  },
  "x-pathmote-limits": {
    "lookup_request_body_bytes": 4096,
    "target_requests_per_lookup": 10,
    "lookup_elapsed_ms": 10000,
    "admission": {
      "per_caller_per_minute": 10,
      "per_caller_per_day": 100,
      "global_per_day": 1000,
      "storage_failure": "fail-closed"
    }
  },
  "paths": {
    "/api/v1/lookup": {
      "post": {
        "operationId": "lookupWebsiteEntryPoints",
        "summary": "Find advertised machine-readable entry points",
        "description": "Disabled by default in this build. When the operator enables controlled-origin lookups, runs the canonical bounded lookup. The JSON body is limited to 4 KiB and must contain exactly one url field. An evaluated target-side miss or failure is represented by a 200 LookupResult; admission and service failures are HTTP errors. Unsupported methods return 405 with Allow: POST.",
        "x-enabled": false,
        "x-read-only": true,
        "x-request-body-max-bytes": 4096,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LookupRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Evaluated lookup, including bounded misses and target-side failures",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LookupResult"
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/InvalidInput" },
          "403": { "$ref": "#/components/responses/DisallowedTarget" },
          "405": { "$ref": "#/components/responses/PostRequired" },
          "413": { "$ref": "#/components/responses/OversizedLookup" },
          "415": { "$ref": "#/components/responses/UnsupportedContentType" },
          "429": { "$ref": "#/components/responses/AdmissionLimit" },
          "503": { "$ref": "#/components/responses/ServiceUnavailable" }
        }
      }
    },
    "/lookup": {
      "post": {
        "operationId": "lookupWebsiteEntryPointsForm",
        "summary": "Submit the ordinary HTML lookup form",
        "description": "Disabled by default in this build; follows the same operator gate and checker as the JSON operation. The URL-encoded body is limited to 4 KiB and must contain exactly one url field. Responses are escaped HTML and are not reusable result-page URLs. Unsupported methods return 405 with Allow: POST.",
        "x-enabled": false,
        "x-read-only": true,
        "x-request-body-max-bytes": 4096,
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/LookupRequest"
              }
            }
          }
        },
        "responses": {
          "200": { "$ref": "#/components/responses/HtmlResponse" },
          "400": { "$ref": "#/components/responses/HtmlErrorResponse" },
          "403": { "$ref": "#/components/responses/HtmlErrorResponse" },
          "405": { "$ref": "#/components/responses/HtmlPostRequired" },
          "413": { "$ref": "#/components/responses/HtmlErrorResponse" },
          "415": { "$ref": "#/components/responses/HtmlErrorResponse" },
          "429": {
            "description": "Admission limit reached; escaped HTML response",
            "headers": {
              "Retry-After": {
                "description": "Seconds or HTTP date after which another attempt may be made",
                "schema": { "type": "string" }
              }
            },
            "content": {
              "text/html": {
                "schema": { "type": "string" }
              }
            }
          },
          "503": { "$ref": "#/components/responses/HtmlErrorResponse" }
        }
      }
    },
    "/api/v1/proposals": {
      "post": {
        "operationId": "proposeEntryPoint",
        "summary": "Proposal intake (disabled)",
        "description": "Disabled in this build: POST returns 503 proposal_disabled before body or storage access. Enabling requires both PATHMOTE_PROPOSALS_ENABLED and PATHMOTE_BASELINE_OBSERVED to equal true, after baseline observations and trial setup. Only then do the 202-new and 200-duplicate receipt contracts apply. Intake never fetches targets or verifies a proposal. Writes require explicit task or standing authorization; viewing or discovering this API does not supply that authority. Unsupported methods return 405 with Allow: POST.",
        "x-enabled": false,
        "x-read-only": false,
        "x-request-body-max-bytes": 8192,
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProposalRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Enabled trial only: existing tuple receipt, with its current pending, accepted or rejected state; no new quota reservation",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ProposalReceipt" } } }
          },
          "202": {
            "description": "Enabled trial only: new durable proposal awaiting independent verification",
            "content": { "application/json": { "schema": {
              "allOf": [
                { "$ref": "#/components/schemas/ProposalReceipt" },
                { "type": "object", "properties": { "state": { "const": "pending" } } }
              ]
            } } }
          },
          "400": { "$ref": "#/components/responses/InvalidInput" },
          "403": { "$ref": "#/components/responses/DisallowedTarget" },
          "405": { "$ref": "#/components/responses/PostRequired" },
          "413": {
            "description": "Enabled trial only: request body exceeds 8 KiB",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
          },
          "415": { "$ref": "#/components/responses/UnsupportedContentType" },
          "429": { "$ref": "#/components/responses/AdmissionLimit" },
          "503": {
            "description": "proposal_disabled means no body/storage access or receipt. Enabled trial failures may instead report capacity_unavailable (Retry-After), proposal_unavailable, admission_unavailable, or submission_unknown. A timed-out D1 write may still finish; retry the same tuple to reconcile, never infer rollback from 503.",
            "headers": { "Retry-After": { "description": "Present when pending-proposal capacity is full", "schema": { "type": "string" } } },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ErrorResponse" },
                "example": {
                  "error": {
                    "code": "proposal_disabled",
                    "message": "Proposal intake is disabled."
                  },
                  "request_id": "0f44acfd-8e06-4ffd-9604-9e279b4bc5f0"
                }
              }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "operationId": "getServiceHealth",
        "summary": "Read the service name and version",
        "description": "Does not fetch a target or expose secrets or database contents.",
        "x-read-only": true,
        "responses": {
          "200": {
            "description": "Minimal service health response",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/HealthResponse" }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "responses": {
      "PostRequired": {
        "description": "Only POST is supported; no lookup or proposal is performed",
        "headers": { "Allow": { "schema": { "type": "string", "const": "POST" } } },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ErrorResponse" } } }
      },
      "HtmlPostRequired": {
        "description": "Only POST is supported; escaped HTML error response",
        "headers": {
          "Allow": { "schema": { "type": "string", "const": "POST" } },
          "Cache-Control": { "schema": { "type": "string", "const": "no-store" } },
          "Referrer-Policy": { "schema": { "type": "string", "const": "no-referrer" } }
        },
        "content": { "text/html": { "schema": { "type": "string" } } }
      },
      "InvalidInput": {
        "description": "Invalid input or malformed encoding",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "DisallowedTarget": {
        "description": "The target is not permitted by the public-destination policy",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "OversizedLookup": {
        "description": "The request body exceeds 4 KiB",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "UnsupportedContentType": {
        "description": "The request content type is unsupported",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "AdmissionLimit": {
        "description": "Admission limit reached",
        "headers": {
          "Retry-After": {
            "description": "Seconds or HTTP date after which another attempt may be made",
            "schema": { "type": "string" }
          }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "ServiceUnavailable": {
        "description": "Lookup disabled, admission storage unavailable, or an internal dependency failed",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/ErrorResponse" }
          }
        }
      },
      "HtmlResponse": {
        "description": "Escaped HTML lookup result",
        "headers": {
          "Cache-Control": {
            "schema": { "type": "string", "const": "no-store" }
          },
          "Referrer-Policy": {
            "schema": { "type": "string", "const": "no-referrer" }
          }
        },
        "content": {
          "text/html": {
            "schema": { "type": "string" }
          }
        }
      },
      "HtmlErrorResponse": {
        "description": "Escaped HTML error response",
        "headers": {
          "Cache-Control": {
            "schema": { "type": "string", "const": "no-store" }
          },
          "Referrer-Policy": {
            "schema": { "type": "string", "const": "no-referrer" }
          }
        },
        "content": {
          "text/html": {
            "schema": { "type": "string" }
          }
        }
      }
    },
    "schemas": {
      "ProposalReceipt": {
        "type": "object",
        "additionalProperties": false,
        "required": ["receipt_id", "state", "received_at"],
        "properties": {
          "receipt_id": { "type": "string", "format": "uuid" },
          "state": { "type": "string", "enum": ["pending", "accepted", "rejected"] },
          "received_at": { "type": "string", "format": "date-time" }
        }
      },
      "LookupRequest": {
        "type": "object",
        "additionalProperties": false,
        "minProperties": 1,
        "maxProperties": 1,
        "required": ["url"],
        "properties": {
          "url": {
            "type": "string",
            "minLength": 1,
            "maxLength": 2048,
            "description": "An HTTP or HTTPS URL using a default port, without embedded credentials."
          }
        }
      },
      "LookupResult": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "schema_version",
          "request_id",
          "target_url",
          "checked_at",
          "cache",
          "status",
          "coverage",
          "findings",
          "checks",
          "limitations"
        ],
        "properties": {
          "schema_version": { "type": "string", "const": "1" },
          "request_id": { "type": "string" },
          "target_url": { "type": "string", "format": "uri" },
          "checked_at": { "type": "string", "format": "date-time" },
          "cache": { "$ref": "#/components/schemas/CacheState" },
          "status": {
            "type": "string",
            "enum": ["found", "not_found", "check_failed"]
          },
          "coverage": {
            "type": "string",
            "enum": ["complete", "partial"]
          },
          "findings": {
            "type": "array",
            "maxItems": 10,
            "items": { "$ref": "#/components/schemas/Finding" }
          },
          "checks": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Check" }
          },
          "limitations": {
            "type": "array",
            "items": { "type": "string" }
          }
        }
      },
      "CacheState": {
        "type": "object",
        "additionalProperties": false,
        "required": ["hit", "expires_at"],
        "properties": {
          "hit": { "type": "boolean" },
          "expires_at": {
            "type": ["string", "null"],
            "format": "date-time"
          }
        }
      },
      "Finding": {
        "type": "object",
        "additionalProperties": false,
        "required": [
          "url",
          "kind",
          "source_url",
          "discovery_method",
          "checked_at",
          "relevance",
          "verification"
        ],
        "properties": {
          "url": { "type": "string", "format": "uri" },
          "kind": {
            "type": "string",
            "enum": ["rss", "atom", "json_feed", "sitemap", "documentation", "api", "mcp"]
          },
          "source_url": { "type": "string", "format": "uri" },
          "discovery_method": { "type": "string" },
          "checked_at": { "type": "string", "format": "date-time" },
          "relevance": {
            "type": "string",
            "enum": ["releases", "general_updates", "unknown"]
          },
          "relevance_evidence": { "type": "string", "maxLength": 200 },
          "verification": { "$ref": "#/components/schemas/FindingVerification" },
          "contribution_id": { "type": "string" }
        }
      },
      "FindingVerification": {
        "type": "object",
        "additionalProperties": false,
        "required": ["advertised", "reachability", "format"],
        "properties": {
          "advertised": { "type": "boolean" },
          "reachability": {
            "type": "string",
            "enum": ["reachable", "unreachable", "not_checked"]
          },
          "format": {
            "type": "string",
            "enum": ["valid", "invalid", "unsupported", "not_checked"]
          }
        }
      },
      "Check": {
        "type": "object",
        "additionalProperties": false,
        "required": ["url", "kind", "outcome"],
        "properties": {
          "url": { "type": "string", "format": "uri" },
          "kind": { "type": "string" },
          "outcome": {
            "type": "string",
            "enum": ["ok", "absent", "blocked", "timeout", "too_large", "invalid", "network_error", "budget_exhausted"]
          },
          "http_status": { "type": "integer", "minimum": 100, "maximum": 599 },
          "duration_ms": { "type": "number", "minimum": 0 }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": ["error", "request_id"],
        "properties": {
          "error": {
            "type": "object",
            "additionalProperties": false,
            "required": ["code", "message"],
            "properties": {
              "code": { "type": "string" },
              "message": { "type": "string" }
            }
          },
          "request_id": { "type": "string" }
        }
      },
      "ProposalRequest": {
        "type": "object",
        "additionalProperties": false,
        "minProperties": 4,
        "maxProperties": 4,
        "required": ["target_url", "candidate_url", "source_url", "kind"],
        "properties": {
          "target_url": { "type": "string", "maxLength": 2048 },
          "candidate_url": { "type": "string", "maxLength": 2048 },
          "source_url": { "type": "string", "maxLength": 2048 },
          "kind": {
            "type": "string",
            "enum": ["rss", "atom", "json_feed", "sitemap", "documentation", "api", "mcp"]
          }
        }
      },
      "HealthResponse": {
        "type": "object",
        "additionalProperties": false,
        "required": ["service", "version"],
        "properties": {
          "service": { "type": "string", "const": "pathmote" },
          "version": { "type": "string", "const": "0.1.0" }
        }
      }
    }
  }
}
