{"openapi":"3.1.0","info":{"title":"Diwash Bhandari Portfolio API","summary":"Contact, profile and agent-discovery endpoints for the Diwash Bhandari portfolio.","description":"Public API for the Diwash Bhandari portfolio — a Backend Engineer & AI Developer based in Kathmandu, Nepal, specialising in FastAPI, scalable API architecture, RAG systems and production AI/ML integration.\n\nAll endpoints are unauthenticated and free to call. Requests are rate limited to 10 per minute per IP; every response carries `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`, and a 429 additionally carries `Retry-After`. Errors are always JSON with a machine-readable `error` code, a human-readable `message` and a `hint` describing how to resolve it.\n\nAgent entry points: this spec, the MCP manifest at `/.well-known/mcp/manifest.json`, the Streamable HTTP MCP endpoint at `/mcp`, and `/llms.txt`. Content pages additionally answer `Accept: text/markdown`.","version":"1.1.0","termsOfService":"https://www.diwash-bhandari.com.np/privacy","contact":{"name":"Diwash Bhandari","email":"dev@diwash-bhandari.com.np","url":"https://www.diwash-bhandari.com.np"},"license":{"name":"MIT","identifier":"MIT"}},"externalDocs":{"description":"Human-readable API documentation","url":"https://www.diwash-bhandari.com.np/api/docs"},"servers":[{"url":"https://www.diwash-bhandari.com.np","description":"Production"}],"security":[],"tags":[{"name":"contact","description":"Send a message and inspect the contact endpoint contract."},{"name":"discovery","description":"Machine-readable descriptions of the site and its API."},{"name":"mcp","description":"Model Context Protocol endpoints exposing the API as tools."},{"name":"content","description":"Page content as markdown for agent consumption."}],"paths":{"/api/contact":{"post":{"operationId":"sendContactMessage","summary":"Send a contact message","description":"Send a message to Diwash Bhandari. Ideal for project inquiries, collaboration proposals, or technical questions about backend development, AI/ML integration, or system architecture. No authentication required.","tags":["contact"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactRequest"},"examples":{"inquiry":{"summary":"Project inquiry","value":{"name":"Jane Doe","email":"jane@example.com","message":"Hi Diwash, I'd like to discuss a backend API project using FastAPI. Can we schedule a call?"}}}}}},"responses":{"200":{"description":"Message sent successfully.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactSuccess"},"examples":{"default":{"value":{"success":true,"message":"Message sent successfully! I'll get back to you within 24-48 hours."}}}}}},"400":{"description":"Validation error. `error` is one of MISSING_FIELDS, INVALID_NAME, INVALID_EMAIL, INVALID_MESSAGE, INVALID_JSON.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"missingFields":{"summary":"Required fields absent","value":{"error":"MISSING_FIELDS","message":"Missing required fields: name, email, message.","hint":"Include all three fields: name (string), email (string), message (string).","timestamp":"2026-08-23T12:00:00.000Z","path":"/api/contact"}},"invalidEmail":{"summary":"Malformed email","value":{"error":"INVALID_EMAIL","message":"Email address is not valid.","hint":"Provide a valid email address like name@example.com.","timestamp":"2026-08-23T12:00:00.000Z","path":"/api/contact"}}}}}},"429":{"description":"Rate limited — more than 10 requests in the 60 second window.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"},"examples":{"rateLimited":{"value":{"error":"RATE_LIMITED","message":"Too many requests. Please wait before retrying.","hint":"Retry after the rate limit window resets (60 seconds).","timestamp":"2026-08-23T12:00:00.000Z","path":"/api/contact"}}}}}},"500":{"description":"Unexpected server error.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"502":{"description":"Downstream email provider failed. Safe to retry with backoff.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"operationId":"getContactEndpointInfo","summary":"Describe the contact endpoint","description":"Returns the contact endpoint contract — accepted methods, required fields and their constraints. Useful for an agent that wants to validate input before posting.","tags":["contact"],"responses":{"200":{"description":"Endpoint contract.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ContactEndpointInfo"}}}}}}},"/api":{"get":{"operationId":"listApiEndpoints","summary":"List API endpoints","description":"Index of every public endpoint with its method and purpose, plus links to the OpenAPI spec, llms.txt and the MCP manifest. A good first call for an agent discovering this API.","tags":["discovery"],"responses":{"200":{"description":"Endpoint index.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiIndex"}}}}}}},"/openapi.json":{"get":{"operationId":"getOpenApiSpec","summary":"Get this OpenAPI specification","description":"Returns this OpenAPI 3.1 document. Also served at /api/openapi.json for backwards compatibility.","tags":["discovery"],"responses":{"200":{"description":"The OpenAPI 3.1 document.","content":{"application/json":{"schema":{"type":"object","description":"An OpenAPI 3.1 document.","additionalProperties":true}}}}}}},"/api/docs":{"get":{"operationId":"getApiDocumentation","summary":"Get API documentation","description":"Human-readable API documentation. Content-negotiated: `Accept: text/html` returns the HTML page, `Accept: text/markdown` returns markdown, and `Accept: application/json` redirects (303) to the OpenAPI spec.","tags":["discovery"],"parameters":[{"$ref":"#/components/parameters/AcceptRepresentation"}],"responses":{"200":{"description":"Documentation in the negotiated representation.","headers":{"Vary":{"$ref":"#/components/headers/Vary"}},"content":{"text/html":{"schema":{"type":"string"}},"text/markdown":{"schema":{"type":"string"}}}},"303":{"description":"Redirect to the OpenAPI spec, when JSON was requested.","headers":{"Location":{"description":"Absolute URL of the OpenAPI document.","schema":{"type":"string","format":"uri"}}}}}}},"/md/{page}":{"get":{"operationId":"getPageMarkdown","summary":"Get page content as markdown","description":"Markdown representation of a content page, rendered from the same data as the HTML. The same documents are returned at the canonical URLs (`/`, `/about`, ...) when the request carries `Accept: text/markdown`.","tags":["content"],"parameters":[{"name":"page","in":"path","required":true,"description":"Which page to render. Omit the segment entirely for the homepage.","schema":{"type":"string","enum":["about","contact","privacy"]}}],"responses":{"200":{"description":"Markdown document.","headers":{"Vary":{"$ref":"#/components/headers/Vary"}},"content":{"text/markdown":{"schema":{"type":"string"}}}},"404":{"description":"No markdown representation for that path; body lists the valid ones.","content":{"text/markdown":{"schema":{"type":"string"}}}}}}},"/mcp":{"post":{"operationId":"callMcpEndpoint","summary":"MCP Streamable HTTP endpoint","description":"Model Context Protocol endpoint over Streamable HTTP, speaking JSON-RPC 2.0. Supports the `initialize`, `tools/list` and `tools/call` methods. Responds with `application/json`, or `text/event-stream` when the client accepts it.","tags":["mcp"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcRequest"},"examples":{"toolsList":{"summary":"List available tools","value":{"jsonrpc":"2.0","id":1,"method":"tools/list"}},"toolsCall":{"summary":"Call a tool","value":{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_profile","arguments":{}}}}}}}},"responses":{"200":{"description":"JSON-RPC response.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}},"202":{"description":"Notification accepted (a JSON-RPC request without an id)."},"400":{"description":"Malformed JSON-RPC envelope.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JsonRpcResponse"}}}}}}},"/.well-known/mcp/tools":{"get":{"operationId":"listMcpTools","summary":"List MCP tools","description":"Plain-JSON listing of the MCP tools and their input schemas, for agents that do not speak JSON-RPC.","tags":["mcp"],"responses":{"200":{"description":"Tool listing.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpToolList"}}}}}}},"/.well-known/mcp/call":{"post":{"operationId":"callMcpTool","summary":"Invoke an MCP tool","description":"Invoke a single MCP tool by name with arguments, using a plain JSON envelope instead of JSON-RPC.","tags":["mcp"],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpCallRequest"},"examples":{"profile":{"value":{"tool":"get_profile","arguments":{}}}}}}},"responses":{"200":{"description":"Tool result.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpCallResponse"}}}},"400":{"description":"Unknown tool or invalid arguments.","content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpCallResponse"}}}},"429":{"description":"Rate limited.","headers":{"RateLimit-Limit":{"$ref":"#/components/headers/RateLimitLimit"},"RateLimit-Remaining":{"$ref":"#/components/headers/RateLimitRemaining"},"RateLimit-Reset":{"$ref":"#/components/headers/RateLimitReset"},"Retry-After":{"$ref":"#/components/headers/RetryAfter"}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/McpCallResponse"}}}}}}}},"components":{"parameters":{"AcceptRepresentation":{"name":"Accept","in":"header","required":false,"description":"Preferred representation.","schema":{"type":"string","enum":["text/html","text/markdown","application/json"],"default":"text/html"}}},"headers":{"RateLimitLimit":{"description":"Requests permitted in the current window (RFC 9239 style).","schema":{"type":"integer","examples":[10]}},"RateLimitRemaining":{"description":"Requests still permitted in the current window.","schema":{"type":"integer","examples":[9]}},"RateLimitReset":{"description":"Unix epoch seconds at which the window resets.","schema":{"type":"integer","examples":[1790045456]}},"RetryAfter":{"description":"Seconds to wait before retrying.","schema":{"type":"integer","examples":[60]}},"Vary":{"description":"Request headers that select this representation.","schema":{"type":"string","examples":["Accept, Accept-Encoding"]}}},"schemas":{"ContactRequest":{"type":"object","title":"ContactRequest","description":"Request body for sending a contact message.","required":["name","email","message"],"additionalProperties":false,"properties":{"name":{"type":"string","description":"Your full name.","minLength":2,"maxLength":100,"examples":["Jane Doe"]},"email":{"type":"string","format":"email","description":"Your email address, used for the reply.","pattern":"^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$","examples":["jane@example.com"]},"message":{"type":"string","description":"Project details, questions, or a collaboration proposal.","minLength":10,"maxLength":2000,"examples":["Hi Diwash, I'd like to discuss a backend API project using FastAPI."]}}},"ContactSuccess":{"type":"object","title":"ContactSuccess","description":"Returned when the message was accepted for delivery.","required":["success","message"],"additionalProperties":false,"properties":{"success":{"type":"boolean","const":true,"description":"Always true on success."},"message":{"type":"string","description":"Human-readable confirmation.","examples":["Message sent successfully! I'll get back to you within 24-48 hours."]}}},"ContactEndpointInfo":{"type":"object","title":"ContactEndpointInfo","description":"Self-description of the contact endpoint.","required":["success","endpoint","method","description","schema"],"properties":{"success":{"type":"boolean","examples":[true]},"endpoint":{"type":"string","examples":["/api/contact"]},"method":{"type":"array","description":"Methods that mutate or read this resource.","items":{"type":"string"},"examples":[["POST"]]},"description":{"type":"string"},"schema":{"type":"object","description":"Field constraints, mirroring ContactRequest.","additionalProperties":true}}},"ApiIndex":{"type":"object","title":"ApiIndex","description":"Index of the public API surface.","required":["name","version","description","base_url","endpoints"],"properties":{"name":{"type":"string"},"version":{"type":"string"},"description":{"type":"string"},"base_url":{"type":"string","format":"uri"},"endpoints":{"type":"array","items":{"type":"object","required":["path","method","description"],"properties":{"path":{"type":"string"},"method":{"type":"string"},"description":{"type":"string"}}}},"docs":{"type":"object","description":"Links to machine-readable descriptions.","additionalProperties":{"type":"string"}}}},"ErrorResponse":{"type":"object","title":"ErrorResponse","description":"Structured error. Every non-2xx JSON response on this API uses this shape, so an agent can branch on `error` and act on `hint`.","required":["error","message","hint","timestamp","path"],"additionalProperties":false,"properties":{"error":{"type":"string","description":"Machine-readable error code.","enum":["MISSING_FIELDS","INVALID_NAME","INVALID_EMAIL","INVALID_MESSAGE","INVALID_JSON","RATE_LIMITED","DELIVERY_FAILED","INTERNAL_ERROR","NOT_FOUND","METHOD_NOT_ALLOWED"]},"message":{"type":"string","description":"Human-readable explanation.","examples":["Email address is not valid."]},"hint":{"type":"string","description":"How to resolve the error.","examples":["Provide a valid email address like name@example.com."]},"timestamp":{"type":"string","format":"date-time","description":"ISO 8601 time the error was produced."},"path":{"type":"string","description":"Request path that produced the error."}}},"JsonRpcRequest":{"type":"object","title":"JsonRpcRequest","description":"JSON-RPC 2.0 request envelope accepted by the MCP endpoint.","required":["jsonrpc","method"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"description":"Request id. Omit for a notification, which is answered with 202.","anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}]},"method":{"type":"string","description":"MCP method to invoke.","enum":["initialize","notifications/initialized","ping","tools/list","tools/call"]},"params":{"type":"object","additionalProperties":true}}},"JsonRpcResponse":{"type":"object","title":"JsonRpcResponse","description":"JSON-RPC 2.0 response envelope. Exactly one of result/error is present.","required":["jsonrpc"],"properties":{"jsonrpc":{"type":"string","const":"2.0"},"id":{"anyOf":[{"type":"string"},{"type":"integer"},{"type":"null"}]},"result":{"type":"object","additionalProperties":true},"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"integer","description":"JSON-RPC error code."},"message":{"type":"string"},"data":{"description":"Optional additional detail."}}}}},"McpTool":{"type":"object","title":"McpTool","description":"An MCP tool definition.","required":["name","description","inputSchema"],"properties":{"name":{"type":"string"},"description":{"type":"string"},"inputSchema":{"type":"object","description":"JSON Schema for the tool arguments.","additionalProperties":true}}},"McpToolList":{"type":"object","title":"McpToolList","description":"The MCP tool listing: the protocol revision, the transport, and every available tool with its input schema.","required":["tools"],"properties":{"mcpVersion":{"type":"string","examples":["2025-06-18"]},"tools":{"type":"array","items":{"$ref":"#/components/schemas/McpTool"}}}},"McpCallRequest":{"type":"object","title":"McpCallRequest","description":"Plain-JSON envelope for invoking a single MCP tool by name, for agents that do not speak JSON-RPC.","required":["tool"],"additionalProperties":false,"properties":{"tool":{"type":"string","description":"Tool name to invoke.","enum":["send_contact_message","get_profile","get_projects","get_experience","get_skills","get_services","get_openapi_spec"]},"arguments":{"type":"object","description":"Arguments matching the tool's inputSchema.","additionalProperties":true}}},"McpCallResponse":{"type":"object","title":"McpCallResponse","description":"Result of an MCP tool invocation. On failure, `error` carries a machine-readable code and `hint` explains how to resolve it.","required":["success"],"properties":{"success":{"type":"boolean"},"result":{"description":"Tool output when success is true."},"error":{"type":"string","description":"Failure reason when success is false."},"hint":{"type":"string","description":"How to resolve the failure."}}}}}}