{
  "openapi": "3.1.0",
  "info": {
    "title": "Bex Website Content API",
    "version": "1.0.0",
    "description": "Public, unauthenticated access to the Bex website documentation, blog, and discovery resources. This is not the Bex platform API and cannot manage services, deployments, or databases. For platform management use https://api.bex.co/mcp; setup: https://bex.co/docs/mcp-server. Platform API contracts are owned by the Bex platform project. Read documentation through /llms.txt and raw .md URLs.",
    "contact": {
      "name": "bex.co",
      "url": "https://bex.co",
      "email": "dev@stargately.com"
    }
  },
  "servers": [
    {
      "url": "https://bex.co",
      "description": "Bex website content (not the platform API)"
    }
  ],
  "security": [],
  "paths": {
    "/healthz": {
      "get": {
        "operationId": "getHealth",
        "summary": "Health check",
        "description": "Liveness probe: returns ok when this website process responds. It does not check platform services or dependencies.",
        "tags": [
          "System"
        ],
        "responses": {
          "200": {
            "description": "Healthy",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "example": "ok"
                }
              }
            }
          }
        }
      }
    },
    "/sitemap.xml": {
      "get": {
        "operationId": "getSitemap",
        "summary": "XML sitemap",
        "description": "Lists all public URLs for crawlers and agents.",
        "tags": [
          "Discovery"
        ],
        "responses": {
          "200": {
            "description": "Sitemap XML",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Unchanged representation with an empty body. GET and HEAD honor If-None-Match (weak comparison, tag lists, and *) and, when Last-Modified is sent, If-Modified-Since. If-None-Match takes precedence. Redirects and errors never become 304."
          }
        }
      }
    },
    "/robots.txt": {
      "get": {
        "operationId": "getRobotsTxt",
        "summary": "Robots.txt",
        "description": "Crawler directives and sitemap pointer.",
        "tags": [
          "Discovery"
        ],
        "responses": {
          "200": {
            "description": "robots.txt",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "getLlmsTxt",
        "summary": "Agent index (llmstxt.org)",
        "description": "Markdown index of all docs for agents. See also /llms-full.txt and /docs/:section/llms.txt",
        "tags": [
          "Discovery"
        ],
        "responses": {
          "200": {
            "description": "Markdown index",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Unchanged representation with an empty body. GET and HEAD honor If-None-Match (weak comparison, tag lists, and *) and, when Last-Modified is sent, If-Modified-Since. If-None-Match takes precedence. Redirects and errors never become 304."
          }
        }
      }
    },
    "/llms-full.txt": {
      "get": {
        "operationId": "getLlmsFullTxt",
        "summary": "Full docs dump",
        "description": "Concatenated markdown of all docs for agent ingestion.",
        "tags": [
          "Discovery"
        ],
        "responses": {
          "200": {
            "description": "Full dump",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Unchanged representation with an empty body. GET and HEAD honor If-None-Match (weak comparison, tag lists, and *) and, when Last-Modified is sent, If-Modified-Since. If-None-Match takes precedence. Redirects and errors never become 304."
          }
        }
      }
    },
    "/content/openapi.json": {
      "get": {
        "operationId": "getOpenApiSpec",
        "summary": "Website content OpenAPI specification",
        "description": "This website content contract. Platform management uses a separate API at api.bex.co.",
        "tags": [
          "Discovery"
        ],
        "responses": {
          "200": {
            "description": "OpenAPI JSON",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "304": {
            "description": "Unchanged representation with an empty body. GET and HEAD honor If-None-Match (weak comparison, tag lists, and *) and, when Last-Modified is sent, If-Modified-Since. If-None-Match takes precedence. Redirects and errors never become 304."
          }
        }
      }
    },
    "/content/platform-openapi.json": {
      "get": {
        "operationId": "getPlatformOpenApiSpec",
        "summary": "Graded mirror of the Bex platform OpenAPI contract (not executed on bex.co)",
        "description": "Pinned Render-compatible platform OpenAPI with per-operation implementation grades (x-bex-grade, x-badges). Served for documentation only; bex.co does not execute these operations. Live management uses https://api.bex.co.",
        "tags": [
          "Discovery"
        ],
        "responses": {
          "200": {
            "description": "Graded platform OpenAPI JSON",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "304": {
            "description": "Unchanged representation with an empty body. GET and HEAD honor If-None-Match (weak comparison, tag lists, and *) and, when Last-Modified is sent, If-Modified-Since. If-None-Match takes precedence. Redirects and errors never become 304."
          }
        }
      }
    },
    "/graphql": {
      "get": {
        "operationId": "getGraphqlIntrospection",
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "summary": "Execute a website content GraphQL query",
        "description": "English blog queries supplied through the query URL parameter. The query parameter is limited to 65536 UTF-8 bytes. Selected operation work is bounded before resolvers run (see POST). GraphiQL is available only in development. JSON request batching is disabled.",
        "tags": [
          "Content API"
        ],
        "responses": {
          "200": {
            "description": "GraphQL response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "414": {
            "description": "Query parameter exceeds the 65536-byte limit. Body is a GraphQL-shaped errors array; not a rate-limit response.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "postGraphql",
        "summary": "Execute GraphQL query",
        "description": "Public, read-only queries against the English website blog. These queries do not manage platform resources. Request bodies are limited to 65536 bytes while reading (including chunked or missing Content-Length). After parse/validation, selected operation work — including aliases, fragments, variables/defaults, include/skip, and clamped list pageSize (max 100) — must stay within a fixed content-work budget before resolvers run. Over-budget operations return GraphQL errors with extensions.code OPERATION_BUDGET_EXCEEDED. Oversized bodies return HTTP 413 with a GraphQL-shaped errors array. These bounds are not platform rate-limits and do not emit rate-limit headers.",
        "tags": [
          "Content API"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "query"
                ],
                "properties": {
                  "query": {
                    "type": "string",
                    "description": "GraphQL query string"
                  },
                  "variables": {
                    "type": "object",
                    "additionalProperties": true
                  },
                  "operationName": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              },
              "examples": {
                "blogPosts": {
                  "value": {
                    "query": "query ListPosts { blogPosts(page:1, pageSize:5){ posts{ title slug date } totalCount } }"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "GraphQL execution result (including over-budget OPERATION_BUDGET_EXCEEDED errors)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object"
                    },
                    "errors": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      }
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "Request body exceeds 65536 bytes. Not a rate-limit response.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/docs/{slug}.md": {
      "get": {
        "operationId": "getDocMarkdown",
        "summary": "Raw markdown for a docs page",
        "description": "Append .md to a canonical English docs page URL. Returns the English markdown source with frontmatter, even when the HTML page has translations. The docs index has no raw markdown representation.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Doc slug e.g. platform/quickstart"
          }
        ],
        "responses": {
          "200": {
            "description": "Markdown source",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "headers": {
              "Vary": {
                "schema": {
                  "type": "string",
                  "example": "Accept, Accept-Encoding"
                }
              }
            }
          },
          "304": {
            "description": "Unchanged representation with an empty body. GET and HEAD honor If-None-Match (weak comparison, tag lists, and *) and, when Last-Modified is sent, If-Modified-Since. If-None-Match takes precedence. Redirects and errors never become 304."
          },
          "404": {
            "description": "Not found",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/blog/{year}/{month}/{day}/{slug}.md": {
      "get": {
        "operationId": "getBlogPostMarkdown",
        "summary": "Raw markdown for a blog post",
        "description": "English markdown for a dated blog post. For a translation, use /{locale}/blog/{year}/{month}/{day}/{slug}.md. Blog indexes, tags, and authors have no raw markdown representation.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "year",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "month",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "day",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Markdown source",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Unchanged representation with an empty body. GET and HEAD honor If-None-Match (weak comparison, tag lists, and *) and, when Last-Modified is sent, If-Modified-Since. If-None-Match takes precedence. Redirects and errors never become 304."
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/{locale}/blog/{year}/{month}/{day}/{slug}.md": {
      "get": {
        "operationId": "getLocalizedBlogPostMarkdown",
        "summary": "Translated raw markdown for a blog post",
        "description": "Returns the translated post when available, falling back to the English source when that translation is missing.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "locale",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "zh",
                "ko",
                "ja",
                "es",
                "pt",
                "ru",
                "de"
              ]
            }
          },
          {
            "name": "year",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "month",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "day",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Markdown source",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Unchanged representation with an empty body. GET and HEAD honor If-None-Match (weak comparison, tag lists, and *) and, when Last-Modified is sent, If-Modified-Since. If-None-Match takes precedence. Redirects and errors never become 304."
          },
          "404": {
            "description": "Not found"
          }
        }
      }
    },
    "/.well-known/oauth-authorization-server": {
      "get": {
        "operationId": "getOAuthMetadata",
        "summary": "OAuth 2.0 authorization server metadata (RFC 8414) — redirects to the issuer",
        "description": "bex.co is not an authorization server. This path 301s to https://oauth.bex.co/.well-known/oauth-authorization-server, the issuer for the platform API and its MCP server (resource metadata: https://api.bex.co/.well-known/oauth-protected-resource).",
        "tags": [
          "Auth"
        ],
        "responses": {
          "301": {
            "description": "Redirect to the issuer's metadata document"
          }
        }
      }
    },
    "/.well-known/mcp.json": {
      "get": {
        "operationId": "getMcpManifest",
        "summary": "MCP manifest",
        "description": "Points agents at the bex platform MCP server (https://api.bex.co/mcp, Streamable HTTP, OAuth 2.1) and at this site's content surfaces (llms.txt, raw markdown, OpenAPI, GraphQL). bex.co hosts no MCP server; /mcp redirects to the setup guide at /docs/mcp-server.",
        "tags": [
          "Discovery"
        ],
        "responses": {
          "200": {
            "description": "MCP manifest",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/releases/{channel}/{version}": {
      "get": {
        "operationId": "getDesktopRelease",
        "summary": "Bex Desktop release lookup",
        "description": "Stable channel only. Resolves GitHub release metadata for bex-desktop. Without /asset or /download, responds 302 to the GitHub release tag page. No authentication. Preview channel returns 404.",
        "tags": [
          "Desktop"
        ],
        "parameters": [
          {
            "name": "channel",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "stable"
              ]
            }
          },
          {
            "name": "version",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "description": "`latest` or a `X.Y.Z-bex.N` version"
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the GitHub release tag"
          },
          "404": {
            "description": "Unknown channel or version"
          },
          "503": {
            "description": "Upstream GitHub unavailable"
          }
        }
      }
    },
    "/releases/{channel}/{version}/download": {
      "get": {
        "operationId": "downloadDesktopAsset",
        "summary": "Bex Desktop asset download redirect",
        "description": "302 to the GitHub asset URL for asset=bex|bex-remote-server (legacy zed aliases accepted), os=macos|linux|windows, arch=aarch64|x86_64. Same-origin path used by /download. No authentication.",
        "tags": [
          "Desktop"
        ],
        "parameters": [
          {
            "name": "channel",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "stable"
              ]
            }
          },
          {
            "name": "version",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "asset",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "bex",
                "bex-remote-server",
                "zed",
                "zed-remote-server"
              ]
            }
          },
          {
            "name": "os",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "macos",
                "linux",
                "windows"
              ]
            }
          },
          {
            "name": "arch",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "aarch64",
                "x86_64"
              ]
            }
          }
        ],
        "responses": {
          "302": {
            "description": "Redirect to the GitHub asset download URL"
          },
          "400": {
            "description": "Missing or unsupported query parameters"
          },
          "404": {
            "description": "Asset not in the release"
          }
        }
      }
    },
    "/api/release_notes/v2/{channel}/{version}": {
      "get": {
        "operationId": "getDesktopReleaseNotes",
        "summary": "Bex Desktop release notes JSON",
        "description": "Returns `{ title, release_notes }` for the stable channel. No authentication. Used by auto-update and the /download page.",
        "tags": [
          "Desktop"
        ],
        "parameters": [
          {
            "name": "channel",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "stable"
              ]
            }
          },
          {
            "name": "version",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Release notes payload",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Unknown channel or version"
          }
        }
      }
    },
    "/releases/{channel}/{version}/asset": {
      "get": {
        "operationId": "getDesktopAssetJson",
        "summary": "Bex Desktop asset URL JSON",
        "description": "Same query parameters as /download (`asset`, `os`, `arch`). Responds 200 with `{ version, url }` instead of redirecting. No authentication.",
        "tags": [
          "Desktop"
        ],
        "parameters": [
          {
            "name": "channel",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "stable"
              ]
            }
          },
          {
            "name": "version",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "asset",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "bex",
                "bex-remote-server",
                "zed",
                "zed-remote-server"
              ]
            }
          },
          {
            "name": "os",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "macos",
                "linux",
                "windows"
              ]
            }
          },
          {
            "name": "arch",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "aarch64",
                "x86_64"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Asset metadata",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "400": {
            "description": "Missing or unsupported query parameters"
          },
          "404": {
            "description": "Asset not in the release"
          }
        }
      }
    },
    "/{locale}/sitemap.xml": {
      "get": {
        "operationId": "getLocaleSitemap",
        "summary": "Locale-prefixed XML sitemap",
        "description": "Same URL set as /sitemap.xml with locale-prefixed loc entries for a non-default locale. Unsupported locale prefixes return 404.",
        "tags": [
          "Discovery"
        ],
        "parameters": [
          {
            "name": "locale",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "zh",
                "ko",
                "ja",
                "es",
                "pt",
                "ru",
                "de"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sitemap XML",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Unchanged representation with an empty body. GET and HEAD honor If-None-Match (weak comparison, tag lists, and *) and, when Last-Modified is sent, If-Modified-Since. If-None-Match takes precedence. Redirects and errors never become 304."
          },
          "404": {
            "description": "Unknown locale"
          }
        }
      }
    },
    "/docs/{section}/llms.txt": {
      "get": {
        "operationId": "getSectionLlmsTxt",
        "summary": "Per-section agent docs index",
        "description": "English llmstxt.org index limited to one top-level docs directory (for example platform, aptos, x402). Unknown sections return 404.",
        "tags": [
          "Discovery"
        ],
        "parameters": [
          {
            "name": "section",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Top-level docs directory name"
          }
        ],
        "responses": {
          "200": {
            "description": "Markdown index",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Unchanged representation with an empty body. GET and HEAD honor If-None-Match (weak comparison, tag lists, and *) and, when Last-Modified is sent, If-Modified-Since. If-None-Match takes precedence. Redirects and errors never become 304."
          },
          "404": {
            "description": "Unknown section"
          }
        }
      }
    },
    "/oauth/client-metadata.json": {
      "get": {
        "operationId": "getOAuthClientMetadata",
        "summary": "OAuth client metadata for Bex Desktop",
        "description": "Public OAuth 2.0 client metadata document used by the Desktop app. No authentication. This is not an authorization server.",
        "tags": [
          "Desktop"
        ],
        "responses": {
          "200": {
            "description": "Client metadata JSON",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/security.txt": {
      "get": {
        "operationId": "getSecurityTxt",
        "summary": "security.txt (RFC 9116)",
        "description": "Vulnerability reporting contact and policy pointers for bex.co. Public, no authentication.",
        "tags": [
          "Trust"
        ],
        "responses": {
          "200": {
            "description": "security.txt",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/manifest.webmanifest": {
      "get": {
        "operationId": "getWebManifest",
        "summary": "Web app manifest",
        "description": "Static PWA manifest served from public/. Linked from site chrome; not a sitemap entry.",
        "tags": [
          "Trust"
        ],
        "responses": {
          "200": {
            "description": "Web app manifest",
            "content": {
              "application/manifest+json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  }
}