{
  "openapi": "3.1.0",
  "info": {
    "title": "Legba Public API",
    "version": "1.0.0",
    "summary": "Read Legba's published security content, contact the team, and submit feedback.",
    "description": "Legba is a Chrome extension with two modes: Ghost loads a page through a route you choose, and Shield opens a page in an isolated browser off your device.\n\nThis API exposes the security content published on legba.app (browser threats, an external attack surface glossary, exposure types, per-application guidance, AI risk topics and articles) plus the site's contact endpoints. Everything is public and unauthenticated.\n\nFor a plain-text overview aimed at language models see /llms.txt. Every page on legba.app also serves Markdown via `Accept: text/markdown`.\n\n## Versioning and deprecation\n\nThe API is versioned in the path (/api/v1). Breaking changes ship under a new path version; additive changes such as new fields or endpoints can land within a version, so parse defensively and ignore unknown fields.\n\nWhen a version is retired it enters a deprecation period of at least six months. During that period every response from the retired version carries a Deprecation header with the date the deprecation began and a Sunset header with the date it stops responding (RFC 9745 and RFC 8594), plus a Link header with rel=successor-version. No version is deprecated today, so those headers are absent.\n\n## Rate limits\n\nRead endpoints allow 600 requests per minute per client and return RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset and RateLimit-Policy. The contact endpoints allow 5 requests per 10 minutes per sender. Limits are enforced per server instance, so the effective ceiling can be higher; treat them as a backstop rather than a quota. Exceeding a limit returns 429 with Retry-After.",
    "contact": {
      "name": "Legba support",
      "email": "support@legba.app",
      "url": "https://www.legba.app/contact"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://www.legba.app/terms-of-service"
    }
  },
  "servers": [
    {
      "url": "https://www.legba.app",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Discovery",
      "description": "Entry points that describe the rest of the API."
    },
    {
      "name": "Content",
      "description": "Read-only access to published security content."
    },
    {
      "name": "Contact",
      "description": "Reach the Legba team. Rate limited."
    }
  ],
  "paths": {
    "/api/v1": {
      "get": {
        "operationId": "getApiIndex",
        "summary": "API index",
        "description": "Lists every collection with its record count and URL templates. Start here when discovering the API.",
        "tags": [
          "Discovery"
        ],
        "responses": {
          "200": {
            "description": "The API index.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiIndex"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method is not supported by this endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/threats": {
      "get": {
        "operationId": "listThreats",
        "summary": "List threats",
        "description": "Browser-borne threat explainers: how each attack works and which controls address it. Returns a paginated list of summaries; fetch a single record for the full detail.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum records to return (1-200). Values outside the range are clamped.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of records to skip before collecting results.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of threats.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SummaryPage"
                }
              }
            }
          },
          "404": {
            "description": "The collection or record does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method is not supported by this endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/threats/{slug}": {
      "get": {
        "operationId": "getThreat",
        "summary": "Get one threat",
        "description": "Returns the complete threat record, including its canonical URL on legba.app.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Slug of the threat, as returned by GET /api/v1/threats.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The threat.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Record"
                }
              }
            }
          },
          "404": {
            "description": "The collection or record does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method is not supported by this endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/glossary": {
      "get": {
        "operationId": "listGlossary",
        "summary": "List glossary",
        "description": "External attack surface management glossary: canonical definitions and related exposures. Returns a paginated list of summaries; fetch a single record for the full detail.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum records to return (1-200). Values outside the range are clamped.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of records to skip before collecting results.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of glossary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SummaryPage"
                }
              }
            }
          },
          "404": {
            "description": "The collection or record does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method is not supported by this endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/glossary/{slug}": {
      "get": {
        "operationId": "getGlossary",
        "summary": "Get one glossary term",
        "description": "Returns the complete glossary term record, including its canonical URL on legba.app.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Slug of the glossary term, as returned by GET /api/v1/glossary.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The glossary term.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Record"
                }
              }
            }
          },
          "404": {
            "description": "The collection or record does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method is not supported by this endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/exposures": {
      "get": {
        "operationId": "listExposures",
        "summary": "List exposures",
        "description": "External attack-surface finding types, with severity, affected technology and validation notes. Returns a paginated list of summaries; fetch a single record for the full detail.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum records to return (1-200). Values outside the range are clamped.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of records to skip before collecting results.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of exposures.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SummaryPage"
                }
              }
            }
          },
          "404": {
            "description": "The collection or record does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method is not supported by this endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/exposures/{slug}": {
      "get": {
        "operationId": "getExposure",
        "summary": "Get one exposure",
        "description": "Returns the complete exposure record, including its canonical URL on legba.app.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Slug of the exposure, as returned by GET /api/v1/exposures.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The exposure.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Record"
                }
              }
            }
          },
          "404": {
            "description": "The collection or record does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method is not supported by this endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/apps": {
      "get": {
        "operationId": "listApps",
        "summary": "List apps",
        "description": "Per-application browser security guidance: common risks and recommended policies by role. Returns a paginated list of summaries; fetch a single record for the full detail.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum records to return (1-200). Values outside the range are clamped.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of records to skip before collecting results.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of apps.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SummaryPage"
                }
              }
            }
          },
          "404": {
            "description": "The collection or record does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method is not supported by this endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/apps/{slug}": {
      "get": {
        "operationId": "getApp",
        "summary": "Get one application guide",
        "description": "Returns the complete application guide record, including its canonical URL on legba.app.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Slug of the application guide, as returned by GET /api/v1/apps.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The application guide.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Record"
                }
              }
            }
          },
          "404": {
            "description": "The collection or record does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method is not supported by this endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/ai": {
      "get": {
        "operationId": "listAi",
        "summary": "List ai",
        "description": "AI and agent browser risks: how each arises and what containment addresses it. Returns a paginated list of summaries; fetch a single record for the full detail.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum records to return (1-200). Values outside the range are clamped.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of records to skip before collecting results.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of ai.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SummaryPage"
                }
              }
            }
          },
          "404": {
            "description": "The collection or record does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method is not supported by this endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/ai/{slug}": {
      "get": {
        "operationId": "getAi",
        "summary": "Get one AI risk topic",
        "description": "Returns the complete AI risk topic record, including its canonical URL on legba.app.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Slug of the AI risk topic, as returned by GET /api/v1/ai.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The AI risk topic.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Record"
                }
              }
            }
          },
          "404": {
            "description": "The collection or record does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method is not supported by this endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/posts": {
      "get": {
        "operationId": "listPosts",
        "summary": "List posts",
        "description": "Published articles on browser isolation, privacy engineering and AI agent security. Returns a paginated list of summaries; fetch a single record for the full detail.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum records to return (1-200). Values outside the range are clamped.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Number of records to skip before collecting results.",
            "schema": {
              "type": "integer",
              "minimum": 0,
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A page of posts.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SummaryPage"
                }
              }
            }
          },
          "404": {
            "description": "The collection or record does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method is not supported by this endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/posts/{slug}": {
      "get": {
        "operationId": "getPost",
        "summary": "Get one blog post",
        "description": "Returns the complete blog post record, including its canonical URL on legba.app.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Slug of the blog post, as returned by GET /api/v1/posts.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9-]+$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The blog post.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Record"
                }
              }
            }
          },
          "404": {
            "description": "The collection or record does not exist.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method is not supported by this endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/search": {
      "get": {
        "operationId": "searchContent",
        "summary": "Search all content",
        "description": "Keyword search across every collection. All terms must match. Title matches rank above summary matches. Use this when you know the topic but not the slug.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Search terms, for example \"session hijacking\".",
            "schema": {
              "type": "string",
              "minLength": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum results to return (1-200).",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching records across all collections.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchResults"
                }
              }
            }
          },
          "400": {
            "description": "The \"q\" parameter was missing or empty.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method is not supported by this endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/pricing": {
      "get": {
        "operationId": "getPricing",
        "summary": "Get pricing",
        "description": "Current pricing for the Legba Chrome extension and the agent plans. The extension part carries both billing intervals, what is included and the install URL. The agentPlans part carries each plan's price, concurrency, what is included and where to start.",
        "tags": [
          "Content"
        ],
        "responses": {
          "200": {
            "description": "Current pricing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Pricing"
                }
              }
            }
          },
          "405": {
            "description": "The HTTP method is not supported by this endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/contact": {
      "post": {
        "operationId": "sendContactMessage",
        "summary": "Send a contact message",
        "description": "Delivers a message to the Legba team and sends the sender an acknowledgement. Rate limited per sender. Use this to reach a human, including about the agent skill.",
        "tags": [
          "Contact"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "email"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "description": "Full name of the person making contact."
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Reply-to address."
                  },
                  "company": {
                    "type": "string",
                    "description": "Company name."
                  },
                  "phone": {
                    "type": "string",
                    "description": "Contact phone number."
                  },
                  "inquiryType": {
                    "type": "string",
                    "description": "What the enquiry is about."
                  },
                  "message": {
                    "type": "string",
                    "maxLength": 5000,
                    "description": "Message body."
                  },
                  "source": {
                    "type": "string",
                    "description": "Page or campaign the enquiry came from."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The message was accepted.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or the email address is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded for this sender.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The message could not be delivered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/feedback": {
      "get": {
        "operationId": "getFeedbackContract",
        "summary": "Get the feedback contract",
        "description": "Describes feedback fields, submission limits, and retry guidance for humans and agents. No authentication is required.",
        "tags": [
          "Discovery",
          "Contact"
        ],
        "responses": {
          "200": {
            "description": "The current feedback submission contract.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeedbackContract"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "submitFeedback",
        "summary": "Submit feedback",
        "description": "Emails a bug report, feature request, or general feedback to phillip@legba.app. No authentication or CAPTCHA is required. Name and reply email are optional; agents must not invent identity. Set reporter to agent for agent submissions. Include only sanitized, relevant reproduction details. No automatic reply is sent. A successful response confirms mail-provider acceptance, not inbox delivery or human review. Limited to 5 submissions per 10 minutes per IP, per server instance.",
        "tags": [
          "Contact"
        ],
        "parameters": [
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": false,
            "description": "Unique key for one submission. Reuse the key and identical body for retries within 24 hours. Stop after success. Without a key, retries can duplicate messages.",
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 256,
              "pattern": "^[A-Za-z0-9._:/-]+$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/FeedbackRequest"
              },
              "example": {
                "type": "bug",
                "message": "Shield did not open after selecting the mode.",
                "reporter": "agent",
                "source": "agent-skill"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The mail provider accepted the feedback.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/FeedbackSuccess"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON, fields, or idempotency key. Follow the code and hint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Submission rate limit exceeded. Wait for Retry-After.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            },
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait before retrying.",
                "schema": {
                  "type": "integer"
                }
              }
            }
          },
          "500": {
            "description": "Feedback could not be accepted. Follow the code and hint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/newsletter": {
      "post": {
        "operationId": "subscribeNewsletter",
        "summary": "Subscribe to the newsletter",
        "description": "Registers an email address for Legba's newsletter. Rate limited per sender.",
        "tags": [
          "Contact"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Address to register for the newsletter."
                  },
                  "source": {
                    "type": "string",
                    "description": "Page the signup came from."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The address was registered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or the email address is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded for this sender.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The message could not be delivered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/waitlist": {
      "post": {
        "operationId": "joinWaitlist",
        "summary": "Join the waitlist",
        "description": "Registers an email address on the Legba waitlist. Rate limited per sender.",
        "tags": [
          "Contact"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Address to register for the waitlist."
                  },
                  "source": {
                    "type": "string",
                    "description": "Page the signup came from."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The address was registered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or the email address is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded for this sender.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The message could not be delivered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/openclaw-early-access": {
      "post": {
        "operationId": "requestOpenClawEarlyAccess",
        "summary": "Request OpenClaw early access",
        "description": "Registers interest in browser isolation for OpenClaw. Rate limited per sender.",
        "tags": [
          "Contact"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "email"
                ],
                "properties": {
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Address to register for OpenClaw early access."
                  },
                  "source": {
                    "type": "string",
                    "description": "Page the signup came from."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "The request was recorded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Success"
                }
              }
            }
          },
          "400": {
            "description": "A required field is missing or the email address is invalid.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded for this sender.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The message could not be delivered.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "FeedbackRequest": {
        "type": "object",
        "required": [
          "type",
          "message"
        ],
        "additionalProperties": false,
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "bug",
              "feature",
              "general"
            ],
            "description": "Feedback category."
          },
          "message": {
            "type": "string",
            "minLength": 10,
            "maxLength": 5000,
            "description": "Feedback after trimming. Include sanitized reproduction details for bugs. Never include secrets."
          },
          "name": {
            "type": "string",
            "maxLength": 100,
            "description": "Optional name. Omit when unknown."
          },
          "email": {
            "type": "string",
            "maxLength": 254,
            "anyOf": [
              {
                "format": "email"
              },
              {
                "const": ""
              }
            ],
            "description": "Optional reply email. Omit when unknown; an empty string is accepted."
          },
          "source": {
            "type": "string",
            "maxLength": 2048,
            "description": "Optional page URL or integration name."
          },
          "reporter": {
            "type": "string",
            "enum": [
              "human",
              "agent"
            ],
            "default": "human",
            "description": "Set to agent when submitted by an agent."
          },
          "agentName": {
            "type": "string",
            "maxLength": 100,
            "description": "Optional agent name. Omit when unknown."
          }
        }
      },
      "FeedbackSuccess": {
        "type": "object",
        "required": [
          "success",
          "messageId"
        ],
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "messageId": {
            "type": "string",
            "description": "Identifier returned by the mail provider."
          }
        }
      },
      "FeedbackContract": {
        "type": "object",
        "required": [
          "endpoint",
          "method",
          "contentType",
          "authentication",
          "types",
          "schema",
          "docs"
        ],
        "properties": {
          "endpoint": {
            "type": "string",
            "const": "/api/feedback"
          },
          "method": {
            "type": "string",
            "const": "POST"
          },
          "contentType": {
            "type": "string",
            "const": "application/json"
          },
          "authentication": {
            "type": "string",
            "const": "none"
          },
          "description": {
            "type": "string"
          },
          "types": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "value",
                "label",
                "description"
              ],
              "properties": {
                "value": {
                  "type": "string",
                  "enum": [
                    "bug",
                    "feature",
                    "general"
                  ]
                },
                "label": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                }
              }
            }
          },
          "schema": {
            "type": "object",
            "description": "JSON Schema describing the accepted submission body."
          },
          "idempotency": {
            "type": "object",
            "properties": {
              "header": {
                "type": "string"
              },
              "required": {
                "type": "boolean"
              },
              "maxLength": {
                "type": "integer"
              },
              "pattern": {
                "type": "string"
              },
              "description": {
                "type": "string"
              }
            }
          },
          "rateLimit": {
            "type": "object",
            "properties": {
              "requests": {
                "type": "integer"
              },
              "windowSeconds": {
                "type": "integer"
              },
              "scope": {
                "type": "string"
              }
            }
          },
          "example": {
            "$ref": "#/components/schemas/FeedbackRequest"
          },
          "docs": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "Error": {
        "type": "object",
        "description": "Every failure returns this shape. Branch on `code`; show `message`; act on `hint`.",
        "required": [
          "error",
          "code",
          "message",
          "hint",
          "docs"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message. Same text as `message`."
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable identifier for the failure.",
            "enum": [
              "not_found",
              "method_not_allowed",
              "invalid_request",
              "invalid_email",
              "missing_field",
              "rate_limited",
              "not_configured",
              "upstream_error",
              "internal_error"
            ]
          },
          "message": {
            "type": "string",
            "description": "Human-readable message."
          },
          "hint": {
            "type": "string",
            "description": "What to change to make the request succeed."
          },
          "docs": {
            "type": "string",
            "format": "uri",
            "description": "Link to this API description."
          }
        }
      },
      "Success": {
        "type": "object",
        "description": "Acknowledgement returned by the contact endpoints.",
        "properties": {
          "success": {
            "type": "boolean",
            "description": "True when the request was accepted."
          },
          "message": {
            "type": "string",
            "description": "Human-readable confirmation."
          }
        }
      },
      "Summary": {
        "type": "object",
        "description": "Compact record used in list and search responses.",
        "required": [
          "slug",
          "title",
          "category",
          "summary",
          "url"
        ],
        "properties": {
          "slug": {
            "type": "string",
            "description": "Identifier used in the item URL."
          },
          "title": {
            "type": "string",
            "description": "Display name of the record."
          },
          "category": {
            "type": "string",
            "description": "Grouping the record belongs to."
          },
          "summary": {
            "type": "string",
            "description": "One or two sentence description."
          },
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Canonical page for this record on legba.app."
          },
          "lastUpdated": {
            "type": "string",
            "format": "date",
            "description": "Date the record last changed."
          }
        }
      },
      "SummaryPage": {
        "type": "object",
        "description": "A page of summaries.",
        "required": [
          "collection",
          "data",
          "total",
          "limit",
          "offset"
        ],
        "properties": {
          "collection": {
            "type": "string",
            "description": "Collection these records belong to."
          },
          "description": {
            "type": "string",
            "description": "What the collection contains."
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Summary"
            },
            "description": "The records in this page."
          },
          "total": {
            "type": "integer",
            "description": "Total records available, ignoring pagination."
          },
          "limit": {
            "type": "integer",
            "description": "Maximum records returned in this page."
          },
          "offset": {
            "type": "integer",
            "description": "Records skipped before this page."
          }
        }
      },
      "Record": {
        "type": "object",
        "description": "A complete record. Fields vary by collection; every record has `slug`, `url` and a summary or definition.",
        "required": [
          "collection",
          "data"
        ],
        "properties": {
          "collection": {
            "type": "string",
            "description": "Collection the record belongs to."
          },
          "data": {
            "type": "object",
            "description": "The record itself.",
            "required": [
              "slug",
              "url"
            ],
            "properties": {
              "slug": {
                "type": "string",
                "description": "Identifier used in the item URL."
              },
              "url": {
                "type": "string",
                "format": "uri",
                "description": "Canonical page on legba.app."
              }
            }
          }
        }
      },
      "SearchResults": {
        "type": "object",
        "description": "Search results across every collection.",
        "required": [
          "query",
          "total",
          "data"
        ],
        "properties": {
          "query": {
            "type": "string",
            "description": "The query that was run."
          },
          "limit": {
            "type": "integer",
            "description": "Maximum results requested."
          },
          "total": {
            "type": "integer",
            "description": "Number of results returned."
          },
          "data": {
            "type": "array",
            "description": "Matching records, best first.",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/Summary"
                },
                {
                  "type": "object",
                  "properties": {
                    "collection": {
                      "type": "string",
                      "description": "Collection the match came from."
                    }
                  }
                }
              ]
            }
          }
        }
      },
      "ApiIndex": {
        "type": "object",
        "description": "Describes the collections and endpoints this API offers.",
        "properties": {
          "name": {
            "type": "string",
            "description": "API name."
          },
          "version": {
            "type": "string",
            "description": "API version."
          },
          "description": {
            "type": "string",
            "description": "What the API covers."
          },
          "openapi": {
            "type": "string",
            "format": "uri",
            "description": "URL of this OpenAPI document."
          },
          "documentation": {
            "type": "string",
            "format": "uri",
            "description": "Human-readable API documentation."
          },
          "collections": {
            "type": "array",
            "description": "Available collections.",
            "items": {
              "type": "object",
              "properties": {
                "key": {
                  "type": "string",
                  "description": "Collection identifier used in URLs."
                },
                "description": {
                  "type": "string",
                  "description": "What the collection contains."
                },
                "list": {
                  "type": "string",
                  "format": "uri",
                  "description": "URL listing the collection."
                },
                "item": {
                  "type": "string",
                  "description": "URL template for a single record."
                },
                "count": {
                  "type": "integer",
                  "description": "Number of published records."
                }
              }
            }
          },
          "endpoints": {
            "type": "array",
            "description": "Endpoints that are not collection-shaped.",
            "items": {
              "type": "object",
              "properties": {
                "method": {
                  "type": "string",
                  "description": "HTTP method."
                },
                "path": {
                  "type": "string",
                  "description": "Endpoint path."
                },
                "description": {
                  "type": "string",
                  "description": "What the endpoint does."
                }
              }
            }
          }
        }
      },
      "Pricing": {
        "type": "object",
        "description": "Current pricing for the Legba Chrome extension and the agent plans.",
        "properties": {
          "product": {
            "type": "string",
            "description": "Product name."
          },
          "description": {
            "type": "string",
            "description": "What the product does."
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217 currency code."
          },
          "plans": {
            "type": "array",
            "description": "Chrome extension billing intervals.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "description": "Stable plan identifier."
                },
                "name": {
                  "type": "string",
                  "description": "Display name."
                },
                "price": {
                  "type": "number",
                  "description": "Amount charged per interval."
                },
                "interval": {
                  "type": "string",
                  "enum": [
                    "month",
                    "year"
                  ],
                  "description": "Billing interval."
                },
                "trial": {
                  "type": "string",
                  "description": "Trial terms."
                }
              }
            }
          },
          "includes": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "What the Chrome extension includes."
          },
          "supportedBrowsers": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Browsers the extension supports."
          },
          "installUrl": {
            "type": "string",
            "format": "uri",
            "description": "Chrome Web Store listing."
          },
          "pricingPage": {
            "type": "string",
            "format": "uri",
            "description": "Human-readable pricing page."
          },
          "agentPlans": {
            "type": "array",
            "description": "Agent plans, for the agent skill and proxy URL. Added 2026-09-30; older fields are unchanged.",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "string",
                  "enum": [
                    "free",
                    "pro",
                    "scale",
                    "enterprise"
                  ],
                  "description": "Stable plan identifier."
                },
                "name": {
                  "type": "string",
                  "description": "Display name."
                },
                "description": {
                  "type": "string",
                  "description": "Who the plan is for."
                },
                "price": {
                  "type": [
                    "number",
                    "null"
                  ],
                  "description": "Amount charged per interval. Null when the price is custom."
                },
                "currency": {
                  "type": "string",
                  "description": "ISO 4217 currency code."
                },
                "interval": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "enum": [
                    "month",
                    null
                  ],
                  "description": "Billing interval. Null when billing is custom."
                },
                "concurrency": {
                  "type": "string",
                  "description": "Concurrent session limit."
                },
                "includes": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  },
                  "description": "What the plan includes."
                },
                "cta": {
                  "type": "object",
                  "description": "How to start on the plan.",
                  "properties": {
                    "label": {
                      "type": "string",
                      "description": "Button label."
                    },
                    "url": {
                      "type": "string",
                      "format": "uri",
                      "description": "Signup page, or the contact page until self-serve signup ships."
                    }
                  }
                }
              }
            }
          },
          "agentPricingPage": {
            "type": "string",
            "format": "uri",
            "description": "Human-readable agent plans section."
          },
          "machineReadable": {
            "type": "string",
            "format": "uri",
            "description": "Markdown pricing document."
          },
          "lastUpdated": {
            "type": "string",
            "format": "date",
            "description": "Date pricing last changed."
          }
        }
      }
    }
  }
}