{"openapi":"3.1.0","info":{"title":"Vivek Sharma — Portfolio API","description":"Public REST API behind Vivek Sharma's portfolio: a health check and the contact-form endpoint.\nSee /docs for the MCP server, which exposes read-only profile/project data as callable tools.\n\n**Versioning policy**: the API is versioned by URL path (/api/v1/*, this document). A breaking change ships as /api/v2/* rather than altering /v1/* in place. The unversioned paths (/api/health, /api/contact) are permanent convenience aliases of the current version's endpoints — they always mirror /v1/*, share the same rate-limit quota, and have no separate removal timeline; they simply aren't where new API surface is added. Every response carries an `API-Version` header naming the version that produced it.","version":"1.0.0","contact":{"name":"Vivek Sharma","email":"er.viveks.sharma@gmail.com","url":"https://the-vivek-sharma.vercel.app"}},"servers":[{"url":"https://the-vivek-sharma.vercel.app","description":"Production"}],"paths":{"/api/v1/health":{"get":{"operationId":"getHealth","summary":"Liveness check","description":"Returns service status and the current server timestamp. No parameters, no auth, not rate-limited. Also reachable, identically, at the unversioned alias /api/health.","parameters":[],"responses":{"200":{"description":"Service is up.","headers":{"API-Version":{"description":"The API version that generated this response.","schema":{"type":"string","example":"1"}}},"content":{"application/json":{"schema":{"type":"object","required":["status","timestamp"],"properties":{"status":{"type":"string","enum":["ok"]},"timestamp":{"type":"string","format":"date-time"}}}}}},"405":{"description":"Method not allowed — this endpoint only supports GET.","headers":{"API-Version":{"description":"The API version that generated this response.","schema":{"type":"string","example":"1"}},"Allow":{"description":"Methods this endpoint supports.","schema":{"type":"string","example":"GET"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}},"/api/v1/contact":{"post":{"operationId":"postContact","summary":"Submit the contact form","description":"Sends a message to the site owner (email + Telegram notification). Rate-limited to 3 requests per IP per 10 minutes and protected by Google reCAPTCHA. Also reachable, identically, at the unversioned alias /api/contact (same rate-limit quota, shared by IP).","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["name","email","message","recaptchaToken"],"properties":{"name":{"type":"string","maxLength":100,"description":"Sender's name."},"email":{"type":"string","format":"email","maxLength":100,"description":"Sender's email address."},"message":{"type":"string","maxLength":500,"description":"Message body."},"recaptchaToken":{"type":"string","description":"Token produced by the client-side Google reCAPTCHA widget."}}}}}},"responses":{"200":{"description":"Message sent successfully.","headers":{"API-Version":{"description":"The API version that generated this response.","schema":{"type":"string","example":"1"}},"RateLimit":{"description":"Current quota status per draft-ietf-httpapi-ratelimit-headers: \"policyname\";r=remaining;t=reset-seconds.","schema":{"type":"string","example":"\"default\";r=2;t=580"}},"RateLimit-Policy":{"description":"The quota policy in force: \"policyname\";q=quota;w=window-seconds.","schema":{"type":"string","example":"\"default\";q=3;w=600"}}},"content":{"application/json":{"schema":{"type":"object","required":["success","message"],"properties":{"success":{"type":"boolean","enum":[true]},"message":{"type":"string"}}}}}},"400":{"description":"Validation failed, or CAPTCHA verification failed.","headers":{"API-Version":{"description":"The API version that generated this response.","schema":{"type":"string","example":"1"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"405":{"description":"Method not allowed — this endpoint only supports POST.","headers":{"API-Version":{"description":"The API version that generated this response.","schema":{"type":"string","example":"1"}},"Allow":{"description":"Methods this endpoint supports.","schema":{"type":"string","example":"POST"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"429":{"description":"Rate limit exceeded (3 requests per IP per 10 minutes).","headers":{"API-Version":{"description":"The API version that generated this response.","schema":{"type":"string","example":"1"}},"RateLimit":{"description":"Current quota status per draft-ietf-httpapi-ratelimit-headers: \"policyname\";r=remaining;t=reset-seconds.","schema":{"type":"string","example":"\"default\";r=2;t=580"}},"RateLimit-Policy":{"description":"The quota policy in force: \"policyname\";q=quota;w=window-seconds.","schema":{"type":"string","example":"\"default\";q=3;w=600"}},"Retry-After":{"description":"Seconds to wait before retrying, per RFC 9110 §10.2.3.","schema":{"type":"integer"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"500":{"description":"Server error — message delivery failed.","headers":{"API-Version":{"description":"The API version that generated this response.","schema":{"type":"string","example":"1"}}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}}}},"components":{"schemas":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"object","required":["code","message"],"properties":{"code":{"type":"string","description":"Stable machine-readable error identifier.","enum":["VALIDATION_ERROR","CAPTCHA_FAILED","RATE_LIMITED","METHOD_NOT_ALLOWED","NOT_FOUND","INTERNAL_ERROR"]},"message":{"type":"string","description":"Human-readable explanation."},"hint":{"type":"string","description":"What the caller should do about it."}}}}}}}}