{
  "openapi": "3.1.0",
  "info": {
    "title": "Legba Public API",
    "version": "1.0.0",
    "summary": "Read Legba's published security content and contact the team.",
    "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, including both billing intervals, what is included and the install URL.",
        "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/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 an email address for early access to the OpenClaw sandbox. 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": {
      "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.",
        "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": "Available 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 every plan 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."
          },
          "machineReadable": {
            "type": "string",
            "format": "uri",
            "description": "Markdown pricing document."
          },
          "lastUpdated": {
            "type": "string",
            "format": "date",
            "description": "Date pricing last changed."
          }
        }
      }
    }
  }
}