{"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.\n\nUse it to:\n- Read the portfolio of shipped client work (`listProjects`, `getProject`) when judging whether the studio has relevant experience.\n- Read the service catalogue and price bands (`listServices`) when estimating budget or scope.\n- Submit a project inquiry (`submitInquiry`) once a user has agreed to make contact.\n\nAll endpoints are public: no API key, OAuth token, or registration is required.\n`submitInquiry` supports a sandbox mode (`\"sandbox\": true`) that validates the payload and returns a\nsimulated response without sending email, so agents can rehearse the call end to end.\n\nErrors 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":[]}