openapi: 3.1.0
info:
  title: Mountain Web Studio Public API
  version: 1.1.0
  summary: "Portfolio, service catalogue, and project-inquiry API for Mountain Web Studio."
  description: |-
    The Mountain Web Studio API lets an agent research the studio and start an engagement on behalf of a user.

    Use it to:
    - Read the portfolio of shipped client work (`listProjects`, `getProject`) when judging whether the studio has relevant experience.
    - Read the service catalogue and price bands (`listServices`) when estimating budget or scope.
    - Submit a project inquiry (`submitInquiry`) once a user has agreed to make contact.

    All endpoints are public: no API key, OAuth token, or registration is required.
    `submitInquiry` supports a sandbox mode (`"sandbox": true`) that validates the payload and returns a
    simulated response without sending email, so agents can rehearse the call end to end.

    Errors are always JSON objects shaped like `{ "error": { "code", "message", "hint", "docs" } }`.
  contact:
    name: Mountain Web Studio Engineering
    email: david@mountain-web-studio.com
    url: "https://mountain-web-studio.com/docs"
  license:
    name: Proprietary
    url: "https://mountain-web-studio.com/terms"
  termsOfService: "https://mountain-web-studio.com/terms"
servers:
  - url: "https://mountain-web-studio.com"
    description: Production
externalDocs:
  description: Mountain Web Studio developer and agent documentation
  url: "https://mountain-web-studio.com/docs"
tags:
  - name: portfolio
    description: Read shipped client work.
  - name: catalogue
    description: "Read services, price bands, and delivery timelines."
  - name: inquiries
    description: Start a conversation with the studio.
  - name: discovery
    description: Machine-readable descriptions of this API.
  - name: agents
    description: Model Context Protocol endpoint.
paths:
  "/api/projects":
    get:
      operationId: listProjects
      tags:
        - portfolio
      summary: List portfolio projects
      description: "Returns every case study in the Mountain Web Studio portfolio as a JSON array, newest first. Call this before recommending the studio so you can cite concrete, comparable work. Add `limit` to trim the response, or `type` to filter by engagement type."
      parameters:
        - name: limit
          in: query
          required: false
          description: Maximum number of projects to return. Omit for all of them.
          schema:
            type: integer
            minimum: 1
            maximum: 100
          example: 5
        - name: type
          in: query
          required: false
          description: "Case-insensitive substring match against the project's engagement type, e.g. \"AI\" or \"Platform\"."
          schema:
            type: string
            maxLength: 80
          example: AI
      responses:
        "200":
          description: The matching portfolio projects.
          content:
            "application/json":
              schema:
                type: array
                items:
                  "$ref": "#/components/schemas/ProjectSummary"
        "422":
          description: A query parameter failed validation.
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/projects/{slug}":
    get:
      operationId: getProject
      tags:
        - portfolio
      summary: Get one portfolio project
      description: "Returns the full case study for a single project, including the narrative description paragraphs and the feature list. Use the `slug` returned by `listProjects`."
      parameters:
        - name: slug
          in: path
          required: true
          description: "URL slug of the project, as returned by listProjects."
          schema:
            type: string
            pattern: "^[a-z0-9-]+$"
            maxLength: 120
          example: betterhealth
      responses:
        "200":
          description: The requested case study.
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ProjectDetail"
        "404":
          description: No project exists with that slug.
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/services":
    get:
      operationId: listServices
      tags:
        - catalogue
      summary: List services and price bands
      description: "Returns the studio's service lines with starting prices, plus the engagement tiers with price ranges and turnaround times. Call this to answer \"what does this cost\" or \"how long will it take\" without guessing."
      parameters:
        - name: country
          in: query
          required: false
          description: "ISO 3166-1 alpha-2 country code of the buyer. Some markets have purchasing-power-adjusted pricing; pass \"PL\" for Poland. Defaults to standard Western-European pricing."
          schema:
            type: string
            pattern: "^[A-Za-z]{2}$"
          example: PL
      responses:
        "200":
          description: Service lines and engagement tiers.
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/ServiceCatalogue"
        "422":
          description: The country code was malformed.
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
  "/api/contact":
    post:
      operationId: submitInquiry
      tags:
        - inquiries
      summary: Submit a project inquiry
      description: "Sends a project inquiry to the studio and emails a confirmation to the address supplied. The studio replies within 48 hours. Only call this after the user has explicitly agreed to be contacted — it delivers real email. Set `sandbox: true` to validate the payload and receive a simulated success response without sending anything."
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/InquiryRequest"
            examples:
              live:
                summary: Real inquiry
                value:
                  name: Dana Reyes
                  email: dana@example.com
                  message: "We want an AI support agent trained on our help centre. Budget around EUR 20k, live before Q4."
              sandbox:
                summary: "Sandbox rehearsal — nothing is sent"
                value:
                  name: Test Agent
                  email: agent@example.com
                  message: Checking the request shape before sending a real inquiry.
                  sandbox: true
      responses:
        "200":
          description: "The inquiry was accepted (or validated, in sandbox mode)."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/InquiryResponse"
        "400":
          description: The request body was not valid JSON.
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
        "422":
          description: A field failed validation.
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
        "500":
          description: "Email delivery failed. The inquiry was not recorded; retry later."
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/Error"
  "/openapi.json":
    get:
      operationId: getOpenApiSpecJson
      tags:
        - discovery
      summary: "This document, as JSON"
      description: Returns the OpenAPI 3.1 description of this API in JSON form.
      responses:
        "200":
          description: The OpenAPI document.
          content:
            "application/json":
              schema:
                type: object
                additionalProperties: true
  "/api/openapi.yaml":
    get:
      operationId: getOpenApiSpecYaml
      tags:
        - discovery
      summary: "This document, as YAML"
      description: Returns the OpenAPI 3.1 description of this API in YAML form.
      responses:
        "200":
          description: The OpenAPI document.
          content:
            "application/yaml":
              schema:
                type: string
  "/api/mcp":
    post:
      operationId: callMcpServer
      tags:
        - agents
      summary: "Model Context Protocol endpoint (Streamable HTTP)"
      description: "JSON-RPC 2.0 endpoint implementing the Model Context Protocol over Streamable HTTP. Supports `initialize`, `tools/list`, `tools/call`, `resources/list`, `resources/read`, and `ping`. Point an MCP client at this URL to use the studio's tools natively instead of calling the REST endpoints yourself."
      requestBody:
        required: true
        content:
          "application/json":
            schema:
              "$ref": "#/components/schemas/JsonRpcRequest"
            example:
              jsonrpc: "2.0"
              id: 1
              method: tools/list
      responses:
        "200":
          description: A JSON-RPC response.
          content:
            "application/json":
              schema:
                "$ref": "#/components/schemas/JsonRpcResponse"
    get:
      operationId: describeMcpServer
      tags:
        - agents
      summary: Describe the MCP endpoint
      description: "Returns the MCP manifest for this server: protocol version, transport, and the tools it exposes. Streamable HTTP GET does not open an SSE stream on this server; use POST for JSON-RPC."
      responses:
        "200":
          description: The MCP manifest.
          content:
            "application/json":
              schema:
                type: object
                additionalProperties: true
components:
  schemas:
    ProjectSummary:
      type: object
      title: ProjectSummary
      description: A portfolio project as returned by listProjects.
      required:
        - slug
        - client
        - type
        - tagline
      properties:
        slug:
          type: string
          description: "URL slug; pass to getProject for the full case study."
          example: betterhealth
        client:
          type: string
          description: Client name.
          example: BetterHealth
        type:
          type: string
          description: Engagement type.
          example: AI Platform
        tagline:
          type: string
          description: One-line summary of the outcome.
          example: Patient intake cut from 20 minutes to 3.
        result:
          type: string
          description: "Alias of tagline, kept for backwards compatibility."
        color:
          type: string
          description: Brand accent colour used in the site UI.
          example: "#34D399"
        url:
          type: string
          format: uri
          description: Canonical case-study page for humans.
      additionalProperties: false
    ProjectDetail:
      type: object
      title: ProjectDetail
      description: A full case study.
      required:
        - slug
        - client
        - type
        - tagline
        - description
        - features
        - publishedAt
      properties:
        slug:
          type: string
        client:
          type: string
        type:
          type: string
        tagline:
          type: string
        description:
          type: array
          description: "Narrative paragraphs, in reading order."
          items:
            type: string
        features:
          type: array
          description: What was built or delivered.
          items:
            type: string
        color:
          type: string
        publishedAt:
          type: string
          format: date
          description: "Publication date, ISO 8601."
        url:
          type: string
          format: uri
      additionalProperties: false
    ServiceCatalogue:
      type: object
      title: ServiceCatalogue
      required:
        - currency
        - services
        - tiers
      properties:
        currency:
          type: string
          description: ISO 4217 code for every price.
          example: EUR
        market:
          type: string
          description: "Country code the returned pricing applies to, or \"default\"."
          example: default
        services:
          type: array
          items:
            type: object
            required:
              - name
              - description
              - pricing
            properties:
              name:
                type: string
                example: "AI Agents & Chatbots"
              description:
                type: string
              pricing:
                type: string
                description: Human-readable starting price.
            additionalProperties: false
        tiers:
          type: array
          items:
            type: object
            required:
              - name
              - range
              - scope
              - turnaround
            properties:
              name:
                type: string
                example: Integration
              range:
                type: string
                example: "€12k – €25k"
              scope:
                type: string
              turnaround:
                type: string
                example: "4–6 weeks"
            additionalProperties: false
      additionalProperties: false
    InquiryRequest:
      type: object
      title: InquiryRequest
      required:
        - name
        - email
        - message
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 200
          description: Full name of the person making the inquiry.
        email:
          type: string
          format: email
          maxLength: 320
          description: Reply-to address. A confirmation email is sent here.
        message:
          type: string
          minLength: 1
          maxLength: 5000
          description: "What the project is. Include scope, budget, and deadline if known — it shortens the reply loop."
        sandbox:
          type: boolean
          default: false
          description: "When true, validate the payload and return a simulated success without sending any email."
      additionalProperties: false
    InquiryResponse:
      type: object
      title: InquiryResponse
      required:
        - ok
      properties:
        ok:
          type: boolean
          description: True when the inquiry was accepted.
        sandbox:
          type: boolean
          description: True when nothing was actually sent because sandbox mode was requested.
        message:
          type: string
          description: Human-readable confirmation.
      additionalProperties: false
    Error:
      type: object
      title: Error
      description: Every non-2xx response from this API uses this shape.
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: string
              description: "Stable machine-readable code. Branch on this, not on the message."
              enum:
                - invalid_json
                - validation_failed
                - not_found
                - method_not_allowed
                - unsupported_media_type
                - delivery_failed
                - internal_error
            message:
              type: string
              description: Human-readable explanation.
            hint:
              type: string
              description: What to change so the next attempt succeeds.
            field:
              type: string
              description: "The offending field, when the error is field-scoped."
            docs:
              type: string
              format: uri
              description: Documentation for this endpoint.
          additionalProperties: false
      additionalProperties: false
    JsonRpcRequest:
      type: object
      title: JsonRpcRequest
      required:
        - jsonrpc
        - method
      properties:
        jsonrpc:
          type: string
          const: "2.0"
        id:
          description: Request id. Omit for notifications.
          anyOf:
            - type: string
            - type: number
        method:
          type: string
          description: MCP method name.
          examples:
            - initialize
            - tools/list
            - tools/call
            - resources/list
            - resources/read
            - ping
        params:
          type: object
          additionalProperties: true
      additionalProperties: false
    JsonRpcResponse:
      type: object
      title: JsonRpcResponse
      required:
        - jsonrpc
      properties:
        jsonrpc:
          type: string
          const: "2.0"
        id:
          anyOf:
            - type: string
            - type: number
            - type: "null"
        result:
          type: object
          additionalProperties: true
        error:
          type: object
          required:
            - code
            - message
          properties:
            code:
              type: integer
            message:
              type: string
            data: {}
          additionalProperties: true
      additionalProperties: false
  securitySchemes: {}
security: []
