{
  "openapi": "3.1.0",
  "info": {
    "title": "BuiltToWinWeb Agent API",
    "version": "1.0.0",
    "description": "Public agent-facing endpoints for built2winweb.com. All read endpoints are anonymous; OAuth (client_credentials) is optional — see /auth.md. Payments use Stripe Checkout (hosted page, human completes payment).",
    "contact": {
      "url": "https://built2winweb.com/contact/"
    }
  },
  "servers": [
    {
      "url": "https://built2winweb.com"
    }
  ],
  "paths": {
    "/mcp": {
      "post": {
        "operationId": "mcp",
        "summary": "MCP server (Streamable HTTP, JSON-RPC 2.0)",
        "description": "Model Context Protocol endpoint. Tools: get_services, get_pricing, get_contact, get_page_markdown. Server card: /.well-known/mcp/server-card.json",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/ask": {
      "get": {
        "operationId": "nlwebAskGet",
        "summary": "Ask a natural-language question about BuiltToWinWeb",
        "description": "NLWeb endpoint. Ranks the site's 167 service, industry and blog pages with BM25 and answers from the site's own 348 FAQ entries when there is a confident match. Tolerates misspellings. Returns Schema.org-typed JSON. Retrieval only: no language model runs on this origin, so any summary is quoted verbatim from the cited page.",
        "tags": [
          "nlweb"
        ],
        "security": [],
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 500
            },
            "description": "The question, in natural language.",
            "example": "do you build websites for restaurants?"
          },
          {
            "name": "site",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Backend scope token. This backend serves only built2winweb.com."
          },
          {
            "name": "prev",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Comma separated previous queries, for context."
          },
          {
            "name": "decontextualized_query",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Fully resolved query. Takes precedence over `query` when present."
          },
          {
            "name": "streaming",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "default": "true"
            },
            "description": "Defaults to true (Server-Sent Events). Pass 0/false for a single JSON object."
          },
          {
            "name": "query_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 128
            },
            "description": "Echoed back. Auto generated when omitted."
          },
          {
            "name": "mode",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "list",
                "summarize",
                "generate"
              ],
              "default": "list"
            },
            "description": "list returns ranked pages; summarize and generate additionally return the site's own FAQ answer when the matcher is confident."
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "default": 5
            },
            "description": "Maximum results. Extension to the NLWeb spec."
          }
        ],
        "responses": {
          "200": {
            "description": "Results. `application/json` when streaming is disabled, otherwise `text/event-stream` carrying the same object in a result frame.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NLWebResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                },
                "example": "data: {\"message_type\":\"result_batch\",\"query_id\":\"b2w-1a2b\",\"results\":[]}\n\ndata: {\"message_type\":\"complete\",\"query_id\":\"b2w-1a2b\"}\n\n"
              }
            }
          },
          "400": {
            "description": "Missing required `query`. Body documents every accepted parameter."
          },
          "429": {
            "description": "Rate limited: 60 queries per 10 minutes per IP."
          }
        }
      },
      "post": {
        "operationId": "nlwebAskPost",
        "summary": "Ask a natural-language question (JSON body)",
        "description": "Same as GET. Accepts application/json or form-encoded bodies; query-string parameters are merged in.",
        "tags": [
          "nlweb"
        ],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/NLWebRequest"
              },
              "example": {
                "query": "how much does a restaurant website cost?",
                "mode": "summarize",
                "streaming": false
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Results. `application/json` when streaming is disabled, otherwise `text/event-stream` carrying the same object in a result frame.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NLWebResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                },
                "example": "data: {\"message_type\":\"result_batch\",\"query_id\":\"b2w-1a2b\",\"results\":[]}\n\ndata: {\"message_type\":\"complete\",\"query_id\":\"b2w-1a2b\"}\n\n"
              }
            }
          },
          "400": {
            "description": "Missing required `query`. Body documents every accepted parameter."
          },
          "429": {
            "description": "Rate limited: 60 queries per 10 minutes per IP."
          }
        }
      }
    },
    "/oauth/register": {
      "post": {
        "operationId": "registerClient",
        "summary": "RFC 7591 dynamic client registration (open)",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "client_name": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "client_id + client_secret"
          }
        }
      }
    },
    "/oauth/token": {
      "post": {
        "operationId": "token",
        "summary": "OAuth 2.0 token endpoint (client_credentials, scope read)",
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "grant_type": {
                    "type": "string",
                    "enum": [
                      "client_credentials"
                    ]
                  },
                  "client_id": {
                    "type": "string"
                  },
                  "client_secret": {
                    "type": "string"
                  }
                },
                "required": [
                  "grant_type",
                  "client_id",
                  "client_secret"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bearer token, 1h expiry"
          },
          "401": {
            "description": "invalid_client"
          }
        }
      }
    },
    "/create_stripe_session.php": {
      "get": {
        "operationId": "createCheckoutSession",
        "summary": "Create a Stripe Checkout session for a fixed-price package",
        "description": "Creates a hosted Stripe Checkout session (mode=payment, USD). The returned URL must be completed by a human in a browser. Rate-limited to 10 sessions/IP/hour. Packages and prices: business = Business Pro Website ($3000 USD); ecommerce = Ecommerce Website ($8000 USD); saas = SaaS / Custom Web Application ($15000 USD); wp_migration = WordPress Migration to Custom PHP ($1950 USD); tech_seo = Technical SEO Optimization ($1040 USD); local_seo = Local SEO and Google Business Profile Optimization ($520 USD); stripe = Stripe Payment Integration ($1040 USD); db_app = Custom Database Application Development ($7280 USD); schema = Schema Markup and Structured Data Implementation ($520 USD); core_vitals = Core Web Vitals and Page Speed Optimization ($975 USD); hosting = Website Hosting Setup and Deployment ($390 USD); maintenance = Website Maintenance and Support ($50 USD). A consultation and Terms acceptance are required before payment.",
        "parameters": [
          {
            "name": "package",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "enum": [
                "business",
                "ecommerce",
                "saas",
                "wp_migration",
                "tech_seo",
                "local_seo",
                "stripe",
                "db_app",
                "schema",
                "core_vitals",
                "hosting",
                "maintenance"
              ]
            },
            "description": "Package key"
          }
        ],
        "responses": {
          "200": {
            "description": "Stripe Checkout session (redirect URL)"
          },
          "400": {
            "description": "Unknown package"
          },
          "429": {
            "description": "Rate limited"
          },
          "403": {
            "description": "Consultation and Terms acceptance required before payment.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "error": {
                      "type": "string",
                      "example": "consultation_required"
                    },
                    "message": {
                      "type": "string"
                    },
                    "bookUrl": {
                      "type": "string"
                    },
                    "termsUrl": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "x-payment-info": {
          "intent": "session",
          "method": "stripe",
          "currency": "USD",
          "amount": 3000,
          "amount_note": "Amount depends on the package parameter; see x-price-table.",
          "x-price-table": {
            "business": 3000,
            "ecommerce": 8000,
            "saas": 15000,
            "wp_migration": 1950,
            "tech_seo": 1040,
            "local_seo": 520,
            "stripe": 1040,
            "db_app": 7280,
            "schema": 520,
            "core_vitals": 975,
            "hosting": 390,
            "maintenance": 50
          },
          "x-price-source": "Generated from the package table in create_stripe_session.php — the same values Stripe charges.",
          "x-consultation-required": true,
          "x-terms-acceptance-required": true,
          "x-agent-completable": false,
          "x-gate-note": "A consultation (or explicit waiver) and Terms of Service acceptance are required before payment and are enforced server-side. A direct GET without the gate is redirected for browsers and returns 403 consultation_required for API clients."
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "description": "OPTIONAL — all endpoints also accept anonymous requests.",
        "flows": {
          "clientCredentials": {
            "tokenUrl": "https://built2winweb.com/oauth/token",
            "scopes": {
              "read": "Read-only access"
            }
          }
        }
      }
    },
    "schemas": {
      "NLWebRequest": {
        "type": "object",
        "required": [
          "query"
        ],
        "properties": {
          "query": {
            "type": "string",
            "maxLength": 500
          },
          "site": {
            "type": "string"
          },
          "prev": {
            "type": "string"
          },
          "decontextualized_query": {
            "type": "string"
          },
          "streaming": {
            "type": [
              "boolean",
              "string"
            ],
            "default": true
          },
          "query_id": {
            "type": "string"
          },
          "mode": {
            "type": "string",
            "enum": [
              "list",
              "summarize",
              "generate"
            ],
            "default": "list"
          },
          "limit": {
            "type": "integer",
            "minimum": 1,
            "maximum": 10,
            "default": 5
          }
        }
      },
      "NLWebResult": {
        "type": "object",
        "properties": {
          "url": {
            "type": "string",
            "format": "uri"
          },
          "name": {
            "type": "string"
          },
          "site": {
            "type": "string"
          },
          "score": {
            "type": "number",
            "description": "0..1, normalised against the best hit for this query."
          },
          "raw_score": {
            "type": "number",
            "description": "Unnormalised BM25 score."
          },
          "description": {
            "type": "string"
          },
          "schema_object": {
            "type": "object",
            "description": "Schema.org object: Service, BlogPosting or WebPage, with a nested FAQPage where the page has FAQs."
          }
        }
      },
      "NLWebResponse": {
        "type": "object",
        "properties": {
          "query_id": {
            "type": "string"
          },
          "query": {
            "type": "string"
          },
          "site": {
            "type": "string"
          },
          "mode": {
            "type": "string"
          },
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NLWebResult"
            }
          },
          "summary": {
            "type": "string",
            "description": "Present in summarize/generate mode."
          },
          "direct_answer": {
            "type": "object",
            "description": "Present in list mode when a FAQ matches confidently."
          },
          "answer_source": {
            "type": "string",
            "enum": [
              "site-faq",
              "retrieval-only",
              "none"
            ]
          },
          "attribution": {
            "type": "object",
            "description": "name + url of the page the answer was quoted from."
          },
          "confidence": {
            "type": "number"
          },
          "spelling_corrections": {
            "type": "object"
          },
          "schema_object": {
            "type": "object",
            "description": "Schema.org ItemList wrapping every result."
          },
          "license": {
            "type": "object"
          }
        }
      }
    }
  },
  "tags": [
    {
      "name": "nlweb",
      "description": "NLWeb natural-language query endpoint.",
      "externalDocs": {
        "url": "https://nlweb.ai/docs/specification"
      }
    }
  ]
}
