{
  "openapi": "3.1.0",
  "info": {
    "title": "murugappan.dev API",
    "version": "1.0.0",
    "summary": "Structured facts about Murugappan M — full stack engineer — for agents and developers.",
    "description": "Read-only JSON access to everything murugappan.dev publishes about Murugappan M — profile, work experience, skills, education, open-source work and blog posts — plus one write endpoint (`POST /api/v1/contact`) for passing along an opportunity.\n\n**When to use this API.** Reach for it when you need grounded facts about Murugappan M as a candidate or collaborator: what he has shipped, which technologies he has production experience with, when he held which role, or what he has written about a technical topic. `GET /api/v1/profile` is the cheapest single call for \"who is this person\"; `GET /api/v1/posts/{slug}` returns a post's full markdown when you need to cite or summarise his writing. Use `POST /api/v1/contact` only to relay a real, specific opportunity or question on a human's behalf.\n\n**When not to use it.** It is not a general-purpose search, resume-parsing or job-matching service, and it holds data about exactly one person.\n\n**Authentication.** None. Every endpoint is public and unauthenticated; no key, token or signup is required. Read endpoints are cached for 5 minutes.\n\n**Versioning.** The version is a path segment: `/api/v1/…`. The unversioned `/api/…` prefix is a permanent alias for `v1` and is never repointed at a later major version, so either form is safe to hard-code. Additive changes ship inside a version without notice — ignore response fields you do not recognise. Breaking changes only ever ship as a new path version. Every response carries `API-Version` and `API-Supported-Versions`; `GET /api/v1/versions` is the machine-readable policy.\n\n**Deprecation.** A deprecated version answers every request with `Deprecation` (RFC 9745) and `Sunset` (RFC 8594) headers plus `Link` relations `deprecation` and `successor-version`, and at least 180 days pass between the first `Deprecation` header and the sunset date. After sunset the version answers `410`. Nothing is currently deprecated: `v1` is current.\n\n**Rate limits.** Every response carries `RateLimit-Policy` and `RateLimit` (draft-ietf-httpapi-ratelimit-headers), mirrored as `X-RateLimit-Limit`, `X-RateLimit-Remaining` and `X-RateLimit-Reset`, and a `429` adds `Retry-After`. Reads have a fair-use ceiling of 600 requests per 60 seconds per client, counted in the edge location that serves you — `\"reads\";q=600;w=60`. `POST /api/v1/contact` really is metered: `\"contact-client\";q=3;w=86400, \"contact-site\";q=20;w=86400` — 3 per client IP per UTC day and 20 site-wide.\n\n**Errors.** Every failure — including 404s on unknown `/api/*` paths — returns the `Error` schema below: a stable `code`, a human `message`, a `hint` describing the fix, and `documentation_url`. No HTML error pages are served under `/api`. Off the API, a request for a path that does not exist gets a real `404` whose body is short markdown pointing at the sitemap and these entry points, so an agent can recover without parsing a styled page.\n\n**MCP.** The same content is served as a Model Context Protocol server (Streamable HTTP) at `POST /mcp`, protocol revision 2026-07-28 with backward compatibility for the `initialize`-based revisions. Eight tools (`get_profile`, `list_experience`, `list_skills`, `list_education`, `list_open_source`, `search_blog_posts`, `get_blog_post`, `send_message`) plus resources for the site's documents and every blog post. Add it to an MCP client as `https://murugappan.dev/mcp` — no auth. Its manifest (`server.json`) is at `https://murugappan.dev/.well-known/mcp.json`.\n\n**Conversational alternative.** The site also runs an AI assistant (\"Jarvis\") over a WebSocket at `/parties/chat-room/{roomId}`, which OpenAPI cannot describe. Send `{\"type\":\"chat\",\"text\":\"...\"}` and read `delta`/`done` frames back. Prefer this API when you want structured data, and the socket when you want a conversation.\n\n**Other machine-readable entry points.** `/.well-known/api-catalog` (RFC 9727 linkset of every API here), `/.well-known/mcp.json` (MCP server manifest), `/mcp` (MCP server), `/llms.txt` (site summary + every blog post), `/AGENTS.md` (agent instructions), `/blog/llms-full.txt` (full post text), `/sitemap.xml`, and `Accept: text/markdown` on any page URL.",
    "contact": {
      "name": "Murugappan M",
      "url": "https://murugappan.dev/developers/",
      "email": "murugu2001@gmail.com"
    },
    "license": {
      "name": "CC BY 4.0",
      "identifier": "CC-BY-4.0"
    }
  },
  "servers": [
    {
      "url": "https://murugappan.dev",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "url": "https://murugappan.dev/developers/",
    "description": "Developer portal: quickstart, examples and agent notes."
  },
  "tags": [
    {
      "name": "profile",
      "description": "Who Murugappan M is: pitch, current role, links and focus areas."
    },
    {
      "name": "resume",
      "description": "Career history: work experience, skills and education, the same data the resume PDF is rendered from."
    },
    {
      "name": "content",
      "description": "Blog posts published at murugappan.dev/blog."
    },
    {
      "name": "contact",
      "description": "Reaching Murugappan M about an opportunity."
    },
    {
      "name": "meta",
      "description": "The API's own machine-readable description."
    }
  ],
  "security": [],
  "paths": {
    "/api/v1/profile": {
      "get": {
        "operationId": "getProfile",
        "summary": "Get the full profile",
        "description": "Returns the canonical summary of Murugappan M: name, headline, elevator pitch, location, email, whether he is open to work, his current role with a start month, his stated focus areas, and every public link (site, about page, blog, RSS, resume PDF, GitHub, LinkedIn, X, developer portal, OpenAPI spec). This is the single cheapest call for grounding an answer about him.",
        "tags": [
          "profile"
        ],
        "responses": {
          "200": {
            "description": "The profile and its links.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Profile"
                }
              }
            }
          },
          "429": {
            "description": "The client's read allowance for the current window is spent (`rate_limited`). `Retry-After` and the `RateLimit` header say when to come back — see the Rate limits section above.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The site's content dataset is missing or unreadable — retry shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/experience": {
      "get": {
        "operationId": "listExperience",
        "summary": "List work experience",
        "description": "Returns every role Murugappan M has held, newest first, each with company, location, the human-readable period, ISO 8601 year-month start and end dates, a `current` flag, a one-line summary and the achievement highlights. Use this rather than parsing the resume PDF when you need dated, per-role facts.",
        "tags": [
          "resume"
        ],
        "responses": {
          "200": {
            "description": "Work history, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ExperienceList"
                }
              }
            }
          },
          "429": {
            "description": "The client's read allowance for the current window is spent (`rate_limited`). `Retry-After` and the `RateLimit` header say when to come back — see the Rate limits section above.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The site's content dataset is missing or unreadable — retry shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/skills": {
      "get": {
        "operationId": "listSkills",
        "summary": "List skills and proficiencies",
        "description": "Returns the technologies Murugappan M works with, grouped into categories (languages, full stack, observability and security, cloud and infrastructure), plus self-reported proficiency levels per broad area. Use this to answer 'does he know X' without inferring it from prose.",
        "tags": [
          "resume"
        ],
        "responses": {
          "200": {
            "description": "Skill categories and proficiency levels.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SkillsResponse"
                }
              }
            }
          },
          "429": {
            "description": "The client's read allowance for the current window is spent (`rate_limited`). `Retry-After` and the `RateLimit` header say when to come back — see the Rate limits section above.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The site's content dataset is missing or unreadable — retry shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/education": {
      "get": {
        "operationId": "listEducation",
        "summary": "List education",
        "description": "Returns formal education: institution, credential, location, the human-readable period, ISO 8601 year-month start and end dates, and any highlights. One entry today; the shape is a list so it stays stable.",
        "tags": [
          "resume"
        ],
        "responses": {
          "200": {
            "description": "Education history.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EducationList"
                }
              }
            }
          },
          "429": {
            "description": "The client's read allowance for the current window is spent (`rate_limited`). `Retry-After` and the `RateLimit` header say when to come back — see the Rate limits section above.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The site's content dataset is missing or unreadable — retry shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/open-source": {
      "get": {
        "operationId": "listOpenSourceContributions",
        "summary": "List open-source contributions",
        "description": "Returns Murugappan M's public open-source work: the project, the role he held, what the contributions were, and links to the individual merged pull requests so a claim can be verified at the source.",
        "tags": [
          "profile"
        ],
        "responses": {
          "200": {
            "description": "Open-source contributions with verifiable links.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenSourceList"
                }
              }
            }
          },
          "429": {
            "description": "The client's read allowance for the current window is spent (`rate_limited`). `Retry-After` and the `RateLimit` header say when to come back — see the Rate limits section above.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The site's content dataset is missing or unreadable — retry shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/posts": {
      "get": {
        "operationId": "listBlogPosts",
        "summary": "List blog posts",
        "description": "Returns every post on the SDE Journey blog, newest first, with its slug, title, canonical URL and summary. Pass the returned `slug` to `getBlogPost` to read a post's full markdown. Optionally narrow the list with a case-insensitive substring query.",
        "tags": [
          "content"
        ],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring matched against post titles and summaries.",
            "schema": {
              "type": "string",
              "maxLength": 200
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of posts to return, newest first. Defaults to all of them.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching posts, newest first.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PostList"
                }
              }
            }
          },
          "400": {
            "description": "A query parameter was not of the documented type or range.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The client's read allowance for the current window is spent (`rate_limited`). `Retry-After` and the `RateLimit` header say when to come back — see the Rate limits section above.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The site's content dataset is missing or unreadable — retry shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/posts/{slug}": {
      "get": {
        "operationId": "getBlogPost",
        "summary": "Get one blog post with its full markdown",
        "description": "Returns a single post's metadata together with its complete markdown source (frontmatter included), so an agent can quote or summarise it without scraping HTML. Slugs come from `listBlogPosts`.",
        "tags": [
          "content"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "The post's slug — the last path segment of its URL, e.g. `cloud-agnostic-rate-limiting`.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The post and its markdown source.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Post"
                }
              }
            }
          },
          "404": {
            "description": "No post exists with that slug — call listBlogPosts for the current set.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The client's read allowance for the current window is spent (`rate_limited`). `Retry-After` and the `RateLimit` header say when to come back — see the Rate limits section above.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "The site's content dataset is missing or unreadable — retry shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/contact": {
      "post": {
        "operationId": "sendContactMessage",
        "summary": "Send Murugappan M a message",
        "description": "Delivers a message to Murugappan M's inbox by email and answers 202 once it is accepted. Send `\"dryRun\": true` first to validate a payload without sending it — that is this endpoint's sandbox, and it spends no allowance. Use it to relay a concrete opportunity, role or question on a human's behalf — include who you are writing for and how to reply. Not for newsletters, bulk outreach or automated pings: the endpoint allows 3 requests per client IP per UTC day and 20 site-wide. No reply is delivered over the API; Murugappan answers the address you supply.",
        "tags": [
          "contact"
        ],
        "requestBody": {
          "required": true,
          "description": "Who is writing, and what about.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ContactRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A dry run: the request is valid and nothing was sent.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactAccepted"
                }
              }
            }
          },
          "202": {
            "description": "The message was accepted for delivery.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactAccepted"
                }
              }
            }
          },
          "400": {
            "description": "The request body was not valid JSON.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "The request body exceeded the size limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "415": {
            "description": "The Content-Type was not application/json.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "One or more fields were invalid; `details` names each one.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The per-client or site-wide daily allowance is spent. Retry after 00:00 UTC.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "503": {
            "description": "Email delivery is not configured or is temporarily unavailable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/versions": {
      "get": {
        "operationId": "getApiVersions",
        "summary": "Get the version and deprecation policy",
        "description": "Returns every version of this API, its status, the release it serves and its sunset date if it has one, together with the policy in force: how versions are selected, what may change inside one, and which headers announce a deprecation. Read this before hard-coding a base path — it is the machine-readable form of the promise the API makes about not changing under you. Also reachable unversioned at `/api/versions`.",
        "tags": [
          "meta"
        ],
        "responses": {
          "200": {
            "description": "The version catalogue and the policy governing it.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiVersions"
                }
              }
            }
          },
          "429": {
            "description": "The client's read allowance for the current window is spent (`rate_limited`). `Retry-After` and the `RateLimit` header say when to come back — see the Rate limits section above.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/openapi.json": {
      "get": {
        "operationId": "getOpenApiSpec",
        "summary": "Get this OpenAPI document",
        "description": "Returns this OpenAPI 3.1.0 document. The canonical location is `/openapi.json` at the site root; this path is the same document served under the API prefix for clients that look there first.",
        "tags": [
          "meta"
        ],
        "responses": {
          "200": {
            "description": "The OpenAPI 3.1.0 description of this API.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "An OpenAPI 3.1.0 document.",
                  "additionalProperties": true
                }
              }
            }
          },
          "429": {
            "description": "The client's read allowance for the current window is spent (`rate_limited`). `Retry-After` and the `RateLimit` header say when to come back — see the Rate limits section above.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Unexpected server error.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {},
    "schemas": {
      "Error": {
        "type": "object",
        "title": "Error",
        "description": "The single error shape every /api/* failure uses. Branch on `error.code`, not on the status text or the message.",
        "required": [
          "error"
        ],
        "additionalProperties": false,
        "properties": {
          "error": {
            "type": "object",
            "description": "The failure.",
            "required": [
              "code",
              "message",
              "hint",
              "documentation_url"
            ],
            "additionalProperties": false,
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable failure code — safe to branch on.",
                "enum": [
                  "not_found",
                  "method_not_allowed",
                  "invalid_request",
                  "unsupported_media_type",
                  "payload_too_large",
                  "rate_limited",
                  "service_unavailable",
                  "internal_error"
                ]
              },
              "message": {
                "type": "string",
                "description": "What went wrong, in one sentence."
              },
              "hint": {
                "type": "string",
                "description": "What to do about it — the corrective action, not a restatement."
              },
              "documentation_url": {
                "type": "string",
                "description": "Where the endpoint is documented.",
                "format": "uri"
              },
              "details": {
                "type": "array",
                "description": "Present on field-level validation failures: one entry per offending field.",
                "items": {
                  "$ref": "#/components/schemas/FieldIssue"
                }
              }
            }
          }
        }
      },
      "FieldIssue": {
        "type": "object",
        "title": "FieldIssue",
        "description": "One rejected request field and why.",
        "required": [
          "field",
          "issue"
        ],
        "additionalProperties": false,
        "properties": {
          "field": {
            "type": "string",
            "description": "The request field that was rejected."
          },
          "issue": {
            "type": "string",
            "description": "What the field must satisfy instead."
          }
        }
      },
      "Link": {
        "type": "object",
        "title": "Link",
        "description": "A labelled public URL.",
        "required": [
          "label",
          "url"
        ],
        "additionalProperties": false,
        "properties": {
          "label": {
            "type": "string",
            "description": "Human-readable name for the destination."
          },
          "url": {
            "type": "string",
            "description": "Absolute URL.",
            "format": "uri"
          }
        }
      },
      "CurrentRole": {
        "type": "object",
        "title": "CurrentRole",
        "description": "The role held right now, if any.",
        "required": [
          "role",
          "company",
          "since"
        ],
        "additionalProperties": false,
        "properties": {
          "role": {
            "type": "string",
            "description": "Job title."
          },
          "company": {
            "type": "string",
            "description": "Employer name."
          },
          "since": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 8601 year-month the role started, or null if unknown.",
            "examples": [
              "2025-12"
            ]
          }
        }
      },
      "Person": {
        "type": "object",
        "title": "Person",
        "description": "The single person this API describes.",
        "required": [
          "name",
          "headline",
          "pitch",
          "location",
          "email",
          "site",
          "availableForWork",
          "currentRole",
          "focus"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "description": "Full name."
          },
          "headline": {
            "type": "string",
            "description": "Professional title.",
            "examples": [
              "Full Stack Engineer"
            ]
          },
          "pitch": {
            "type": "string",
            "description": "Elevator pitch, as published on the site."
          },
          "location": {
            "type": "string",
            "description": "City and country he is based in."
          },
          "email": {
            "type": "string",
            "description": "Public contact address.",
            "format": "email"
          },
          "site": {
            "type": "string",
            "description": "Canonical site URL.",
            "format": "uri"
          },
          "availableForWork": {
            "type": "boolean",
            "description": "Whether he is open to new opportunities."
          },
          "currentRole": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/CurrentRole"
              },
              {
                "type": "null"
              }
            ],
            "description": "The role held right now, or null between roles."
          },
          "focus": {
            "type": "array",
            "description": "What he does, in his own words — one statement per line of the site's 'What I do' section.",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Profile": {
        "type": "object",
        "title": "Profile",
        "description": "Response body of getProfile.",
        "required": [
          "person",
          "links"
        ],
        "additionalProperties": false,
        "properties": {
          "person": {
            "$ref": "#/components/schemas/Person"
          },
          "links": {
            "type": "array",
            "description": "Every public link, including machine-readable ones.",
            "items": {
              "$ref": "#/components/schemas/Link"
            }
          }
        }
      },
      "ExperienceEntry": {
        "type": "object",
        "title": "ExperienceEntry",
        "description": "One role in the work history.",
        "required": [
          "role",
          "company",
          "location",
          "period",
          "startDate",
          "endDate",
          "current",
          "summary",
          "highlights"
        ],
        "additionalProperties": false,
        "properties": {
          "role": {
            "type": "string",
            "description": "Job title."
          },
          "company": {
            "type": "string",
            "description": "Employer name."
          },
          "location": {
            "type": "string",
            "description": "Where the role was based."
          },
          "period": {
            "type": "string",
            "description": "The range exactly as the site displays it.",
            "examples": [
              "December 2025 – Present"
            ]
          },
          "startDate": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 8601 year-month the role started, or null if unparseable.",
            "examples": [
              "2025-12"
            ]
          },
          "endDate": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 8601 year-month the role ended; null while it is ongoing.",
            "examples": [
              "2025-12"
            ]
          },
          "current": {
            "type": "boolean",
            "description": "Whether this is the role held right now."
          },
          "summary": {
            "type": "string",
            "description": "One line on what the role was about."
          },
          "highlights": {
            "type": "array",
            "description": "Concrete achievements in the role.",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ExperienceList": {
        "type": "object",
        "title": "ExperienceList",
        "description": "Response body of listExperience.",
        "required": [
          "experience"
        ],
        "additionalProperties": false,
        "properties": {
          "experience": {
            "type": "array",
            "description": "Roles, newest first.",
            "items": {
              "$ref": "#/components/schemas/ExperienceEntry"
            }
          }
        }
      },
      "SkillCategory": {
        "type": "object",
        "title": "SkillCategory",
        "description": "One group of related technologies.",
        "required": [
          "category",
          "skills"
        ],
        "additionalProperties": false,
        "properties": {
          "category": {
            "type": "string",
            "description": "Group name.",
            "examples": [
              "Cloud & Infra"
            ]
          },
          "skills": {
            "type": "array",
            "description": "The individual technologies in the group.",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "Proficiency": {
        "type": "object",
        "title": "Proficiency",
        "description": "Self-reported depth in a broad area.",
        "required": [
          "area",
          "tools",
          "level"
        ],
        "additionalProperties": false,
        "properties": {
          "area": {
            "type": "string",
            "description": "The area being rated."
          },
          "tools": {
            "type": "array",
            "description": "Named technologies within the area.",
            "items": {
              "type": "string"
            }
          },
          "level": {
            "type": "integer",
            "description": "Self-reported level from 0 to 100.",
            "minimum": 0,
            "maximum": 100
          }
        }
      },
      "SkillsResponse": {
        "type": "object",
        "title": "SkillsResponse",
        "description": "Response body of listSkills.",
        "required": [
          "skills",
          "proficiencies"
        ],
        "additionalProperties": false,
        "properties": {
          "skills": {
            "type": "array",
            "description": "Technologies grouped by category.",
            "items": {
              "$ref": "#/components/schemas/SkillCategory"
            }
          },
          "proficiencies": {
            "type": "array",
            "description": "Self-reported depth per broad area.",
            "items": {
              "$ref": "#/components/schemas/Proficiency"
            }
          }
        }
      },
      "EducationEntry": {
        "type": "object",
        "title": "EducationEntry",
        "description": "One formal qualification.",
        "required": [
          "institution",
          "credential",
          "location",
          "period",
          "startDate",
          "endDate",
          "grade",
          "highlights"
        ],
        "additionalProperties": false,
        "properties": {
          "institution": {
            "type": "string",
            "description": "School or university name."
          },
          "credential": {
            "type": "string",
            "description": "The degree or certificate earned."
          },
          "location": {
            "type": "string",
            "description": "Where the institution is."
          },
          "period": {
            "type": "string",
            "description": "The range exactly as the site displays it."
          },
          "startDate": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 8601 year-month of enrolment, or null."
          },
          "endDate": {
            "type": [
              "string",
              "null"
            ],
            "description": "ISO 8601 year-month of completion, or null."
          },
          "grade": {
            "type": [
              "string",
              "null"
            ],
            "description": "Final grade as the site displays it (e.g. \"CGPA 9.53 / 10\"), or null."
          },
          "highlights": {
            "type": "array",
            "description": "Notable details about the studies.",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "EducationList": {
        "type": "object",
        "title": "EducationList",
        "description": "Response body of listEducation.",
        "required": [
          "education"
        ],
        "additionalProperties": false,
        "properties": {
          "education": {
            "type": "array",
            "description": "Qualifications, newest first.",
            "items": {
              "$ref": "#/components/schemas/EducationEntry"
            }
          }
        }
      },
      "OpenSourceContribution": {
        "type": "object",
        "title": "OpenSourceContribution",
        "description": "Public contributions to one project.",
        "required": [
          "project",
          "role",
          "description",
          "links"
        ],
        "additionalProperties": false,
        "properties": {
          "project": {
            "type": "string",
            "description": "The project contributed to.",
            "examples": [
              "AnkiDroid"
            ]
          },
          "role": {
            "type": "string",
            "description": "The role held on the project."
          },
          "description": {
            "type": "string",
            "description": "What the contributions were."
          },
          "links": {
            "type": "array",
            "description": "Links to the individual merged pull requests, so the claim can be checked at the source.",
            "items": {
              "$ref": "#/components/schemas/Link"
            }
          }
        }
      },
      "OpenSourceList": {
        "type": "object",
        "title": "OpenSourceList",
        "description": "Response body of listOpenSourceContributions.",
        "required": [
          "openSource"
        ],
        "additionalProperties": false,
        "properties": {
          "openSource": {
            "type": "array",
            "description": "One entry per project.",
            "items": {
              "$ref": "#/components/schemas/OpenSourceContribution"
            }
          }
        }
      },
      "PostSummary": {
        "type": "object",
        "title": "PostSummary",
        "description": "A blog post without its body.",
        "required": [
          "slug",
          "title",
          "url",
          "description"
        ],
        "additionalProperties": false,
        "properties": {
          "slug": {
            "type": "string",
            "description": "Identifier to pass to getBlogPost.",
            "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$"
          },
          "title": {
            "type": "string",
            "description": "Post title."
          },
          "url": {
            "type": "string",
            "description": "Canonical URL of the post.",
            "format": "uri"
          },
          "description": {
            "type": "string",
            "description": "One-line summary of the post."
          }
        }
      },
      "PostList": {
        "type": "object",
        "title": "PostList",
        "description": "Response body of listBlogPosts.",
        "required": [
          "posts",
          "count"
        ],
        "additionalProperties": false,
        "properties": {
          "posts": {
            "type": "array",
            "description": "Matching posts, newest first.",
            "items": {
              "$ref": "#/components/schemas/PostSummary"
            }
          },
          "count": {
            "type": "integer",
            "description": "How many posts are in `posts`.",
            "minimum": 0
          }
        }
      },
      "Post": {
        "type": "object",
        "title": "Post",
        "description": "Response body of getBlogPost.",
        "required": [
          "slug",
          "title",
          "url",
          "description",
          "markdown"
        ],
        "additionalProperties": false,
        "properties": {
          "slug": {
            "type": "string",
            "description": "The post's slug."
          },
          "title": {
            "type": "string",
            "description": "Post title."
          },
          "url": {
            "type": "string",
            "description": "Canonical URL of the post.",
            "format": "uri"
          },
          "description": {
            "type": "string",
            "description": "One-line summary of the post."
          },
          "markdown": {
            "type": "string",
            "description": "The post's complete markdown source, frontmatter included."
          }
        }
      },
      "ApiVersionRecord": {
        "type": "object",
        "title": "ApiVersionRecord",
        "description": "One version of this API and where it is in its lifecycle.",
        "required": [
          "version",
          "status",
          "release",
          "basePath",
          "url",
          "specUrl",
          "releasedOn",
          "deprecatedOn",
          "sunsetOn",
          "successor"
        ],
        "additionalProperties": false,
        "properties": {
          "version": {
            "type": "string",
            "description": "The path segment that selects this version.",
            "examples": [
              "v1"
            ]
          },
          "status": {
            "type": "string",
            "description": "`current` while it is the newest, `deprecated` once a successor exists and a sunset date is set, `sunset` once it stops answering.",
            "enum": [
              "current",
              "deprecated",
              "sunset"
            ]
          },
          "release": {
            "type": "string",
            "description": "The semantic release this version serves right now — the value of the `API-Version` response header.",
            "examples": [
              "1.0.0"
            ]
          },
          "basePath": {
            "type": "string",
            "description": "Path prefix every endpoint of this version has.",
            "examples": [
              "/api/v1"
            ]
          },
          "url": {
            "type": "string",
            "description": "Absolute base URL of this version.",
            "format": "uri"
          },
          "specUrl": {
            "type": "string",
            "description": "Absolute URL of this version's OpenAPI document.",
            "format": "uri"
          },
          "releasedOn": {
            "type": "string",
            "description": "ISO 8601 date the version was published.",
            "format": "date"
          },
          "deprecatedOn": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "ISO 8601 date the version was marked deprecated, or null while it is current. Mirrors the `Deprecation` response header."
          },
          "sunsetOn": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "description": "ISO 8601 date the version stops answering, or null while it is current. Mirrors the `Sunset` response header."
          },
          "successor": {
            "type": [
              "string",
              "null"
            ],
            "description": "The version to migrate to, or null when this is the newest."
          }
        }
      },
      "ApiVersionPolicy": {
        "type": "object",
        "title": "ApiVersionPolicy",
        "description": "The rules governing how this API changes.",
        "required": [
          "scheme",
          "deprecationNoticeDays",
          "rules",
          "documentationUrl",
          "headers"
        ],
        "additionalProperties": false,
        "properties": {
          "scheme": {
            "type": "string",
            "description": "How a client selects a version.",
            "enum": [
              "url-path"
            ]
          },
          "deprecationNoticeDays": {
            "type": "integer",
            "description": "Minimum days between a version's first `Deprecation` header and its sunset date.",
            "minimum": 0
          },
          "rules": {
            "type": "array",
            "description": "The policy in full sentences, one commitment per entry — the same text the developer portal publishes.",
            "items": {
              "type": "string"
            }
          },
          "documentationUrl": {
            "type": "string",
            "description": "Where the policy is documented for people.",
            "format": "uri"
          },
          "headers": {
            "type": "object",
            "description": "The response headers that carry version and deprecation state, each mapped to what it means.",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "UnversionedAlias": {
        "type": "object",
        "title": "UnversionedAlias",
        "description": "The unversioned path prefix and the version it is permanently pinned to.",
        "required": [
          "basePath",
          "pinnedTo",
          "note"
        ],
        "additionalProperties": false,
        "properties": {
          "basePath": {
            "type": "string",
            "description": "The unversioned prefix.",
            "examples": [
              "/api"
            ]
          },
          "pinnedTo": {
            "type": "string",
            "description": "The version it always resolves to."
          },
          "note": {
            "type": "string",
            "description": "The promise made about it, in one sentence."
          }
        }
      },
      "ApiVersions": {
        "type": "object",
        "title": "ApiVersions",
        "description": "Response body of getApiVersions.",
        "required": [
          "current",
          "currentRelease",
          "unversionedAlias",
          "versions",
          "policy"
        ],
        "additionalProperties": false,
        "properties": {
          "current": {
            "type": "string",
            "description": "The newest version's path segment."
          },
          "currentRelease": {
            "type": "string",
            "description": "The semantic release the newest version serves."
          },
          "unversionedAlias": {
            "$ref": "#/components/schemas/UnversionedAlias"
          },
          "versions": {
            "type": "array",
            "description": "Every version this deployment knows about, newest first.",
            "items": {
              "$ref": "#/components/schemas/ApiVersionRecord"
            }
          },
          "policy": {
            "$ref": "#/components/schemas/ApiVersionPolicy"
          }
        }
      },
      "ContactRequest": {
        "type": "object",
        "title": "ContactRequest",
        "description": "Request body of sendContactMessage.",
        "required": [
          "email",
          "message"
        ],
        "additionalProperties": false,
        "properties": {
          "email": {
            "type": "string",
            "description": "Reply-to address. Murugappan answers here, so it must be an address the sender actually reads.",
            "format": "email",
            "maxLength": 254
          },
          "message": {
            "type": "string",
            "description": "What you are writing about. Be specific: the role or project, the stack, and anything that needs a decision.",
            "minLength": 20,
            "maxLength": 4000
          },
          "name": {
            "type": "string",
            "description": "Who the message is from.",
            "maxLength": 120
          },
          "company": {
            "type": "string",
            "description": "The company or team you are writing for.",
            "maxLength": 120
          },
          "dryRun": {
            "type": "boolean",
            "description": "Set true to validate the request without sending anything and without spending a rate-limit slot — the sandbox for this endpoint. Answers 200 with status `validated` instead of 202 with status `accepted`.",
            "default": false
          }
        }
      },
      "ContactAccepted": {
        "type": "object",
        "title": "ContactAccepted",
        "description": "Response body of a successful sendContactMessage.",
        "required": [
          "status",
          "message"
        ],
        "additionalProperties": false,
        "properties": {
          "status": {
            "type": "string",
            "description": "`accepted` when the message was queued for delivery, `validated` when the request was a dry run.",
            "enum": [
              "accepted",
              "validated"
            ]
          },
          "message": {
            "type": "string",
            "description": "Human-readable confirmation."
          }
        }
      }
    }
  }
}