{"openapi":"3.1.0","info":{"title":"Prajwol Ghimire · Portfolio API","version":"1.0.0","summary":"APIs, agents, and the boring infrastructure that makes them feel magical.","description":"The personal portfolio of Prajwol Ghimire. Each route reads JSON from `/data` and returns a `{ data, meta }` envelope.\n\n**This whole API is real.** The terminal at the homepage hits these exact endpoints.","contact":{"name":"Prajwol Ghimire","email":"prajwolghimire19@gmail.com","url":"https://www.prajwolghimire.com.np"},"license":{"name":"MIT"}},"servers":[{"url":"https://prajwolghimire.com.np","description":"production"}],"tags":[{"name":"data","description":"Read the JSON data files"},{"name":"ai","description":"prajwol-bot — RAG over the data, streams answers"},{"name":"meta","description":"Search, sitemap, RSS, OpenAPI"},{"name":"admin","description":"Privileged. Requires ADMIN_KEY."}],"paths":{"/api/whoami":{"get":{"tags":["data"],"summary":"Profile of the site owner","responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","additionalProperties":true},"meta":{"type":"object","properties":{"count":{"type":"integer","nullable":true},"responseTime":{"type":"integer","description":"milliseconds"},"status":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"},"endpoint":{"type":"string"}},"required":["responseTime","status","timestamp","endpoint"]}},"required":["data","meta"]}}}}}}},"/api/projects":{"get":{"tags":["data"],"summary":"List projects","parameters":[{"name":"status","in":"query","schema":{"type":"string","enum":["shipped","in-progress","archived"]}},{"name":"tag","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string"},"name":{"type":"string"},"desc":{"type":"string"},"stack":{"type":"array","items":{"type":"string"}},"tags":{"type":"array","items":{"type":"string"}},"metrics":{"type":"object","additionalProperties":true},"status":{"type":"string","enum":["shipped","in-progress","archived"]},"year":{"type":"integer"},"repo":{"type":"string","nullable":true},"live":{"type":"string","nullable":true},"writeup":{"type":"string","nullable":true}}}},"meta":{"type":"object","properties":{"count":{"type":"integer","nullable":true},"responseTime":{"type":"integer","description":"milliseconds"},"status":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"},"endpoint":{"type":"string"}},"required":["responseTime","status","timestamp","endpoint"]}},"required":["data","meta"]}}}}}}},"/api/skills":{"get":{"tags":["data"],"summary":"Skill groups","parameters":[{"name":"group","in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":"OK"}}}},"/api/experience":{"get":{"tags":["data"],"summary":"Work experience","parameters":[{"name":"type","in":"query","schema":{"type":"string","enum":["full-time","internship","freelance"]}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"company":{"type":"string"},"role":{"type":"string"},"period":{"type":"string"},"location":{"type":"string"},"type":{"type":"string"},"highlights":{"type":"array","items":{"type":"string"}}}}},"meta":{"type":"object","properties":{"count":{"type":"integer","nullable":true},"responseTime":{"type":"integer","description":"milliseconds"},"status":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"},"endpoint":{"type":"string"}},"required":["responseTime","status","timestamp","endpoint"]}},"required":["data","meta"]}}}}}}},"/api/blog":{"get":{"tags":["data"],"summary":"List blog posts (metadata only)","parameters":[{"name":"tag","in":"query","schema":{"type":"string"}},{"name":"limit","in":"query","schema":{"type":"integer","minimum":1,"maximum":100}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"date":{"type":"string","format":"date"},"readTime":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}}}}},"meta":{"type":"object","properties":{"count":{"type":"integer","nullable":true},"responseTime":{"type":"integer","description":"milliseconds"},"status":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"},"endpoint":{"type":"string"}},"required":["responseTime","status","timestamp","endpoint"]}},"required":["data","meta"]}}}}}}},"/api/blog/{slug}":{"get":{"tags":["data"],"summary":"Read a single blog post (with markdown body)","parameters":[{"name":"slug","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"allOf":[{"type":"object","properties":{"slug":{"type":"string"},"title":{"type":"string"},"summary":{"type":"string"},"date":{"type":"string","format":"date"},"readTime":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}}}},{"type":"object","properties":{"body":{"type":"string"}}}]},"meta":{"type":"object","properties":{"count":{"type":"integer","nullable":true},"responseTime":{"type":"integer","description":"milliseconds"},"status":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"},"endpoint":{"type":"string"}},"required":["responseTime","status","timestamp","endpoint"]}},"required":["data","meta"]}}}},"404":{"description":"Error envelope","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"status":{"type":"integer"},"details":{}}},"meta":{"type":"object","properties":{"count":{"type":"integer","nullable":true},"responseTime":{"type":"integer","description":"milliseconds"},"status":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"},"endpoint":{"type":"string"}},"required":["responseTime","status","timestamp","endpoint"]}}}}}}}}},"/api/contact":{"post":{"tags":["data"],"summary":"Send a message via Resend","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"from":{"type":"string","format":"email"},"name":{"type":"string"},"subject":{"type":"string","minLength":2,"maxLength":200},"message":{"type":"string","minLength":10,"maxLength":5000}},"required":["from","subject","message"]}}}},"responses":{"201":{"description":"Sent"},"202":{"description":"Accepted but not delivered (transport not configured)"},"400":{"description":"Error envelope","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"status":{"type":"integer"},"details":{}}},"meta":{"type":"object","properties":{"count":{"type":"integer","nullable":true},"responseTime":{"type":"integer","description":"milliseconds"},"status":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"},"endpoint":{"type":"string"}},"required":["responseTime","status","timestamp","endpoint"]}}}}}},"429":{"description":"Error envelope","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"status":{"type":"integer"},"details":{}}},"meta":{"type":"object","properties":{"count":{"type":"integer","nullable":true},"responseTime":{"type":"integer","description":"milliseconds"},"status":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"},"endpoint":{"type":"string"}},"required":["responseTime","status","timestamp","endpoint"]}}}}}}}}},"/api/resume":{"get":{"tags":["data"],"summary":"Resume metadata + download URL","responses":{"200":{"description":"OK"}}}},"/api/search":{"get":{"tags":["meta"],"summary":"Substring search across all data","parameters":[{"name":"q","in":"query","required":true,"schema":{"type":"string","minLength":2}},{"name":"limit","in":"query","schema":{"type":"integer","maximum":50}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"array","items":{"type":"object","properties":{"source":{"type":"string","enum":["profile","project","skill","experience","blog"]},"title":{"type":"string"},"preview":{"type":"string"},"ref":{"type":"string","nullable":true}}}},"meta":{"type":"object","properties":{"count":{"type":"integer","nullable":true},"responseTime":{"type":"integer","description":"milliseconds"},"status":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"},"endpoint":{"type":"string"}},"required":["responseTime","status","timestamp","endpoint"]}},"required":["data","meta"]}}}}}}},"/api/ask":{"post":{"tags":["ai"],"summary":"Ask prajwol-bot (non-streaming)","description":"RAG pipeline: embed the question → retrieve top-5 chunks from `/data` → answer with prajwol-1.8-model. Pass `sessionId` for conversational memory.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"question":{"type":"string","minLength":3,"maxLength":500},"sessionId":{"type":"string","pattern":"^[a-z0-9-]{6,64}$"}},"required":["question"]}}}},"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"type":"object","properties":{"data":{"type":"object","properties":{"answer":{"type":"string"},"sources":{"type":"array","items":{"type":"object"}},"followups":{"type":"array","items":{"type":"string"}},"brand":{"type":"string","example":"prajwol-bot"},"model":{"type":"string","example":"prajwol-1.8-model"},"latency":{"type":"integer"}}},"meta":{"type":"object","properties":{"count":{"type":"integer","nullable":true},"responseTime":{"type":"integer","description":"milliseconds"},"status":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"},"endpoint":{"type":"string"}},"required":["responseTime","status","timestamp","endpoint"]}},"required":["data","meta"]}}}},"429":{"description":"Error envelope","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"status":{"type":"integer"},"details":{}}},"meta":{"type":"object","properties":{"count":{"type":"integer","nullable":true},"responseTime":{"type":"integer","description":"milliseconds"},"status":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"},"endpoint":{"type":"string"}},"required":["responseTime","status","timestamp","endpoint"]}}}}}}}}},"/api/ask/stream":{"post":{"tags":["ai"],"summary":"Ask prajwol-bot (Server-Sent Events stream)","description":"Returns a `text/event-stream`. Events: `sources` → `delta`* (token-by-token) → `followups` → `done`. On error: `error` event with a `message`.","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"question":{"type":"string"},"sessionId":{"type":"string"}},"required":["question"]}}}},"responses":{"200":{"description":"SSE stream","content":{"text/event-stream":{"schema":{"type":"string"}}}}}}},"/api/github":{"get":{"tags":["meta"],"summary":"Live GitHub activity (cached 10 min)","responses":{"200":{"description":"OK"}}}},"/api/openapi.json":{"get":{"tags":["meta"],"summary":"This document","responses":{"200":{"description":"OK"}}}},"/feed.xml":{"get":{"tags":["meta"],"summary":"RSS 2.0 feed of the blog","responses":{"200":{"description":"OK"}}}},"/api/admin/auth":{"get":{"tags":["admin"],"summary":"Auth status","responses":{"200":{"description":"OK"}}},"post":{"tags":["admin"],"summary":"Login with ADMIN_KEY (sets a signed httpOnly cookie)","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"key":{"type":"string"}}}}}},"responses":{"200":{"description":"OK"},"401":{"description":"Error envelope","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"status":{"type":"integer"},"details":{}}},"meta":{"type":"object","properties":{"count":{"type":"integer","nullable":true},"responseTime":{"type":"integer","description":"milliseconds"},"status":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"},"endpoint":{"type":"string"}},"required":["responseTime","status","timestamp","endpoint"]}}}}}}}},"delete":{"tags":["admin"],"summary":"Logout","responses":{"200":{"description":"OK"}}}},"/api/admin/files":{"get":{"tags":["admin"],"summary":"List editable JSON files","responses":{"200":{"description":"OK"},"401":{"description":"Error envelope","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"status":{"type":"integer"},"details":{}}},"meta":{"type":"object","properties":{"count":{"type":"integer","nullable":true},"responseTime":{"type":"integer","description":"milliseconds"},"status":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"},"endpoint":{"type":"string"}},"required":["responseTime","status","timestamp","endpoint"]}}}}}}}}},"/api/admin/files/{name}":{"get":{"tags":["admin"],"summary":"Read a JSON file","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"OK"},"401":{"description":"Error envelope","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"status":{"type":"integer"},"details":{}}},"meta":{"type":"object","properties":{"count":{"type":"integer","nullable":true},"responseTime":{"type":"integer","description":"milliseconds"},"status":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"},"endpoint":{"type":"string"}},"required":["responseTime","status","timestamp","endpoint"]}}}}}}}},"put":{"tags":["admin"],"summary":"Save a JSON file (validates JSON)","parameters":[{"name":"name","in":"path","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"content":{"type":"string"}}}}}},"responses":{"200":{"description":"Saved"},"401":{"description":"Error envelope","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"status":{"type":"integer"},"details":{}}},"meta":{"type":"object","properties":{"count":{"type":"integer","nullable":true},"responseTime":{"type":"integer","description":"milliseconds"},"status":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"},"endpoint":{"type":"string"}},"required":["responseTime","status","timestamp","endpoint"]}}}}}},"422":{"description":"Error envelope","content":{"application/json":{"schema":{"type":"object","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"status":{"type":"integer"},"details":{}}},"meta":{"type":"object","properties":{"count":{"type":"integer","nullable":true},"responseTime":{"type":"integer","description":"milliseconds"},"status":{"type":"integer"},"timestamp":{"type":"string","format":"date-time"},"endpoint":{"type":"string"}},"required":["responseTime","status","timestamp","endpoint"]}}}}}}}}}}}