{
  "openapi": "3.1.0",
  "info": {
    "title": "Select Interactive Public API",
    "version": "1.0.0",
    "summary": "Agent-ready HTTP surface for Select Interactive.",
    "description": "Select Interactive is a custom web development agency in Fort Worth, TX. This document describes the public HTTP endpoints that AI agents and developer tooling can call to query company content, submit project inquiries, and chat with the company assistant. All endpoints are CORS-open for GET, JSON over HTTPS, and rate-limited per IP. Pricing for custom development engagements is handled out of band via the contact and blueprint flows.",
    "contact": {
      "name": "Select Interactive",
      "email": "contact@select-interactive.com",
      "url": "https://www.select-interactive.com/contact"
    },
    "license": {
      "name": "Proprietary",
      "url": "https://www.select-interactive.com/privacy-policy"
    }
  },
  "servers": [
    {
      "url": "https://www.select-interactive.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "assistant",
      "description": "Conversational AI endpoints powered by xAI Grok."
    },
    {
      "name": "intake",
      "description": "Project inquiry and blueprint submission endpoints."
    },
    {
      "name": "content",
      "description": "Public content statistics and metadata."
    },
    {
      "name": "status",
      "description": "Service health and build version information."
    }
  ],
  "paths": {
    "/api/ask": {
      "post": {
        "tags": [
          "assistant"
        ],
        "summary": "Ask the Select Interactive assistant a question.",
        "description": "Streams an assistant reply as Server-Sent Events using the AG-UI event format. The assistant has been grounded with company context (services, case studies, team, news) via a system prompt. Per-IP rate limited via a sliding-window counter (defaults: 10 questions per hour). On rate-limit hits the response is a normal 200 SSE stream carrying a human-readable rate-limit message and a `Retry-After` header in seconds.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AskRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "AG-UI event stream of the assistant reply.",
            "headers": {
              "Retry-After": {
                "description": "Seconds until the next request is allowed (rate-limit replies only).",
                "schema": {
                  "type": "integer",
                  "minimum": 1
                }
              }
            },
            "content": {
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body or schema mismatch.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Assistant is not configured (missing XAI_API_KEY).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "assistant"
        ],
        "summary": "Probe the assistant endpoint.",
        "description": "Returns a small JSON payload for uptime monitors and browsers.",
        "responses": {
          "200": {
            "description": "Probe acknowledgement.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "type": "boolean"
                    },
                    "message": {
                      "type": "string"
                    }
                  },
                  "required": [
                    "ok",
                    "message"
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/api/contact": {
      "post": {
        "tags": [
          "intake"
        ],
        "summary": "Submit a project inquiry.",
        "description": "Validates the request, persists it to Firestore (best-effort), sends a Slack notification (fire-and-forget), and emails the team via SendGrid. Returns 503 if SendGrid is not configured. There is no email confirmation to the submitter from this endpoint.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Submission accepted and email dispatched.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ok": {
                      "const": true
                    }
                  },
                  "required": [
                    "ok"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON body or schema mismatch.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "502": {
            "description": "Upstream email send failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Contact form is not configured (missing SendGrid).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/blueprint": {
      "post": {
        "tags": [
          "intake"
        ],
        "summary": "Submit a project blueprint intake.",
        "description": "Validates and sanitizes the intake, applies anti-bot heuristics (honeypot + minimum fill time), then streams a plain-text AI-generated project overview back to the client. Per-IP and per-email rate limited. When XAI is unavailable the endpoint falls back to a deterministic templated overview keyed by project branch.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BlueprintRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Plain-text blueprint overview stream.",
            "headers": {
              "X-Blueprint-Source": {
                "description": "Whether the overview was streamed from Grok or rendered from a fallback template.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "grok",
                    "fallback",
                    "error"
                  ]
                }
              },
              "X-Blueprint-Branch": {
                "description": "Resolved blueprint branch identifier.",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "Request body too large.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/content-stats": {
      "get": {
        "tags": [
          "content"
        ],
        "summary": "Counts of public content categories.",
        "description": "Returns the counts of published case studies, news articles, and services that back the public site. Useful for agents that want to size a crawl or surface \"we have N case studies\" facts.",
        "responses": {
          "200": {
            "description": "Content statistics.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContentStats"
                }
              }
            }
          },
          "503": {
            "description": "Content statistics are temporarily unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "tags": [
          "status"
        ],
        "summary": "Liveness check.",
        "description": "Returns `status: ok` if the server is responding. Add `?deep=1` plus a bearer token (`HEALTH_CHECK_TOKEN`) for a deep probe that includes Firestore connectivity.",
        "parameters": [
          {
            "name": "deep",
            "in": "query",
            "description": "Set to `1` to run a deep probe (requires authorization).",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Healthy.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          },
          "401": {
            "description": "Deep probe requires a valid bearer token.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Server is degraded or deep probe is unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/version": {
      "get": {
        "tags": [
          "status"
        ],
        "summary": "Build and deployment metadata.",
        "responses": {
          "200": {
            "description": "Current build identifiers.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VersionResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AskRequest": {
        "type": "object",
        "required": [
          "messages"
        ],
        "properties": {
          "messages": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "$ref": "#/components/schemas/AskMessage"
            }
          },
          "data": {
            "type": "object",
            "description": "Optional client metadata. Free-form key/value pairs.",
            "additionalProperties": true
          }
        },
        "additionalProperties": false
      },
      "AskMessage": {
        "type": "object",
        "required": [
          "id",
          "role",
          "parts"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "role": {
            "type": "string",
            "enum": [
              "user",
              "assistant",
              "system"
            ]
          },
          "parts": {
            "type": "array",
            "description": "AG-UI message parts. Each part has a `type` discriminator; text parts use `{ type: \"text\", content: string }`.",
            "items": {
              "type": "object",
              "additionalProperties": true
            }
          }
        },
        "additionalProperties": true
      },
      "ContactRequest": {
        "type": "object",
        "required": [
          "budgetBand",
          "projectType",
          "timeline",
          "hasExistingSite",
          "requirements",
          "email",
          "name",
          "phone",
          "serviceOfInterest"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "company": {
            "type": "string"
          },
          "phone": {
            "type": "string",
            "description": "E.164-normalizable phone number."
          },
          "projectType": {
            "type": "string",
            "enum": [
              "website",
              "web-app",
              "cms",
              "ai",
              "redesign",
              "not-sure"
            ]
          },
          "budgetBand": {
            "type": "string",
            "minLength": 1
          },
          "timeline": {
            "type": "string",
            "minLength": 1
          },
          "hasExistingSite": {
            "type": "string",
            "enum": [
              "yes",
              "no"
            ]
          },
          "existingUrl": {
            "type": "string",
            "format": "uri"
          },
          "existingStack": {
            "type": "string"
          },
          "requirements": {
            "type": "string",
            "minLength": 1,
            "maxLength": 1000
          },
          "serviceOfInterest": {
            "type": "string",
            "minLength": 1
          }
        },
        "additionalProperties": true
      },
      "BlueprintRequest": {
        "type": "object",
        "required": [
          "name",
          "email",
          "phone",
          "projectType",
          "industryDescription",
          "primaryGoal",
          "audienceSize",
          "hasExistingSite",
          "budgetBand",
          "timeline"
        ],
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 80
          },
          "email": {
            "type": "string",
            "format": "email",
            "maxLength": 120
          },
          "phone": {
            "type": "string",
            "minLength": 1
          },
          "company": {
            "type": "string",
            "maxLength": 120
          },
          "projectType": {
            "type": "string",
            "description": "Blueprint project type enum."
          },
          "industryDescription": {
            "type": "string",
            "minLength": 1,
            "maxLength": 240
          },
          "primaryGoal": {
            "type": "string",
            "description": "Blueprint primary goal enum."
          },
          "audienceSize": {
            "type": "string",
            "description": "Blueprint audience size enum."
          },
          "hasExistingSite": {
            "type": "string",
            "enum": [
              "yes",
              "no"
            ]
          },
          "existingUrl": {
            "type": "string",
            "maxLength": 200
          },
          "featureChecklist": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string",
              "minLength": 1,
              "maxLength": 80
            }
          },
          "integrations": {
            "type": "string",
            "maxLength": 240
          },
          "budgetBand": {
            "type": "string"
          },
          "timeline": {
            "type": "string"
          },
          "stackPreferences": {
            "type": "array",
            "maxItems": 20,
            "items": {
              "type": "string"
            }
          }
        },
        "additionalProperties": true
      },
      "ContentStats": {
        "type": "object",
        "description": "Counts of published content surfaces.",
        "additionalProperties": {
          "type": "integer",
          "minimum": 0
        }
      },
      "HealthResponse": {
        "type": "object",
        "required": [
          "status",
          "timestamp"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "ok",
              "degraded"
            ]
          },
          "timestamp": {
            "type": "string",
            "format": "date-time"
          },
          "checks": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "properties": {
                "ok": {
                  "type": "boolean"
                }
              },
              "required": [
                "ok"
              ],
              "additionalProperties": true
            }
          }
        },
        "additionalProperties": false
      },
      "VersionResponse": {
        "type": "object",
        "required": [
          "version",
          "gitCommit",
          "buildId",
          "deployTitle"
        ],
        "properties": {
          "version": {
            "type": "string"
          },
          "gitCommit": {
            "type": "string"
          },
          "buildId": {
            "type": "string"
          },
          "deployTitle": {
            "type": "string"
          },
          "deployedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        },
        "additionalProperties": false
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "field": {
            "type": "string"
          }
        },
        "additionalProperties": true
      }
    }
  },
  "externalDocs": {
    "url": "https://www.select-interactive.com/developers",
    "description": "Developer portal: agent surfaces, usage examples, and MCP integration."
  }
}