{
  "openapi": "3.1.0",
  "info": {
    "title": "Anvilry public API",
    "version": "1.0.0",
    "summary": "Read-only access to Sairam Ugge's portfolio content for people, scripts and AI agents.",
    "description": "Everything here is a GET (plus one MCP POST) over content that is also published as web pages. There are no API keys and no accounts. The documented routes apply no quota of their own; please keep request volume reasonable. Pages negotiate their format: send `Accept: text/markdown` to any page that has a Markdown twin and you get Markdown, send `Accept: text/html` and you get the page. Errors on JSON routes use one shape (see the Error schema). Content-Signal preference: search=yes, ai-input=yes, ai-train=no (robots.txt). The version below is the version of this API description, not of the site.",
    "contact": {
      "name": "Sairam Ugge",
      "url": "https://anvilry.vercel.app/contact",
      "email": "uggesairam0000@gmail.com"
    }
  },
  "servers": [
    {
      "url": "https://anvilry.vercel.app",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Developer guide with copy-paste examples",
    "url": "https://anvilry.vercel.app/developers"
  },
  "security": [],
  "tags": [
    {
      "name": "Content",
      "description": "Portfolio pages and their Markdown twins."
    },
    {
      "name": "Discovery",
      "description": "Machine-readable indexes: llms.txt, feeds, sitemap and the OpenAPI document itself."
    },
    {
      "name": "Data",
      "description": "Structured JSON: résumé and GitHub statistics."
    },
    {
      "name": "MCP",
      "description": "Model Context Protocol server."
    }
  ],
  "paths": {
    "/": {
      "get": {
        "operationId": "getHomepage",
        "summary": "Get the homepage as HTML or Markdown",
        "description": "Content-negotiated: `Accept: text/markdown` returns a Markdown summary of the portfolio (profile, impact, production work, projects, stack, contact and a list of agent resources); `Accept: text/html` returns the web page. The response carries `Vary: Accept`, and the Markdown form is also available at /index.md.",
        "tags": [
          "Content"
        ],
        "responses": {
          "200": {
            "description": "The homepage in the negotiated format.",
            "content": {
              "text/html": {
                "schema": {
                  "type": "string"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "406": {
            "description": "The Accept header asks for Markdown but rules out both Markdown and HTML (for example `text/markdown;q=0, text/html;q=0`).",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/index.md": {
      "get": {
        "operationId": "getHomepageMarkdown",
        "summary": "Get the homepage as Markdown",
        "description": "The Markdown twin of the homepage at a fixed URL, for clients that cannot set an Accept header.",
        "tags": [
          "Content"
        ],
        "responses": {
          "200": {
            "description": "Get the homepage as Markdown",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/projects/{slug}.md": {
      "get": {
        "operationId": "getProjectMarkdown",
        "summary": "Get a project write-up as Markdown",
        "description": "Returns the project write-up at https://anvilry.vercel.app/projects/{slug} as Markdown, with the front matter removed. The same document is served by requesting the HTML page URL with \"Accept: text/markdown\". An unknown slug answers 404 with a short Markdown body that links to https://anvilry.vercel.app/llms.txt.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "URL slug of a projects item, as listed in https://anvilry.vercel.app/llms.txt or returned by the list_all_content MCP tool.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]+$",
              "minLength": 1,
              "maxLength": 120
            },
            "example": "trelix"
          }
        ],
        "responses": {
          "200": {
            "description": "The project write-up as Markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No item with this slug (Markdown body; this route is not JSON).",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/work/{slug}.md": {
      "get": {
        "operationId": "getWorkMarkdown",
        "summary": "Get a production case study as Markdown",
        "description": "Returns the production case study at https://anvilry.vercel.app/work/{slug} as Markdown, with the front matter removed. The same document is served by requesting the HTML page URL with \"Accept: text/markdown\". An unknown slug answers 404 with a short Markdown body that links to https://anvilry.vercel.app/llms.txt.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "URL slug of a work item, as listed in https://anvilry.vercel.app/llms.txt or returned by the list_all_content MCP tool.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]+$",
              "minLength": 1,
              "maxLength": 120
            },
            "example": "pensieve"
          }
        ],
        "responses": {
          "200": {
            "description": "The production case study as Markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No item with this slug (Markdown body; this route is not JSON).",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/articles/{slug}.md": {
      "get": {
        "operationId": "getArticleMarkdown",
        "summary": "Get a article as Markdown",
        "description": "Returns the article at https://anvilry.vercel.app/articles/{slug} as Markdown, with the front matter removed. The same document is served by requesting the HTML page URL with \"Accept: text/markdown\". An unknown slug answers 404 with a short Markdown body that links to https://anvilry.vercel.app/llms.txt. Cross-posted articles are summaries that link to where the full text lives.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "URL slug of a articles item, as listed in https://anvilry.vercel.app/llms.txt or returned by the list_all_content MCP tool.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]+$",
              "minLength": 1,
              "maxLength": 120
            },
            "example": "feature-flags-at-scale-devto"
          }
        ],
        "responses": {
          "200": {
            "description": "The article as Markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No item with this slug (Markdown body; this route is not JSON).",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/notes/{slug}.md": {
      "get": {
        "operationId": "getNoteMarkdown",
        "summary": "Get a engineering note as Markdown",
        "description": "Returns the engineering note at https://anvilry.vercel.app/notes/{slug} as Markdown, with the front matter removed. The same document is served by requesting the HTML page URL with \"Accept: text/markdown\". An unknown slug answers 404 with a short Markdown body that links to https://anvilry.vercel.app/llms.txt. Notes are switched off on a default build, and while they are off every slug answers 404; check /llms.txt for the notes that are published.",
        "tags": [
          "Content"
        ],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "URL slug of a notes item, as listed in https://anvilry.vercel.app/llms.txt or returned by the list_all_content MCP tool.",
            "schema": {
              "type": "string",
              "pattern": "^[A-Za-z0-9_-]+$",
              "minLength": 1,
              "maxLength": 120
            },
            "example": "how-dns-works"
          }
        ],
        "responses": {
          "200": {
            "description": "The engineering note as Markdown.",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "404": {
            "description": "No item with this slug (Markdown body; this route is not JSON).",
            "content": {
              "text/markdown": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/resume.json": {
      "get": {
        "operationId": "getResumeJson",
        "summary": "Get the résumé as JSON Resume",
        "description": "The structured résumé in the JSON Resume format: basics, production work, projects, skills and awards. Built from the same content as the site, so it cannot drift from it.",
        "tags": [
          "Data"
        ],
        "responses": {
          "200": {
            "description": "The résumé document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResumeJson"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/github/stats": {
      "get": {
        "operationId": "getGithubStats",
        "summary": "Get aggregate GitHub statistics",
        "description": "Stars, forks, languages, follower and repository counts across the owner's listed repositories, cached for an hour. Never errors on an upstream failure: counts fall back to zero (repository count to the listed repositories) and the latest push to null.",
        "tags": [
          "Data"
        ],
        "responses": {
          "200": {
            "description": "The aggregate statistics.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GithubStats"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "getLlmsTxt",
        "summary": "Get the llms.txt site index",
        "description": "A plain-text index for language models: who this site is about, when to use it, availability, every production case study and project with a one-line summary, and the links an agent needs.",
        "tags": [
          "Discovery"
        ],
        "responses": {
          "200": {
            "description": "Get the llms.txt site index",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/llms-full.txt": {
      "get": {
        "operationId": "getLlmsFullTxt",
        "summary": "Get the full text corpus",
        "description": "The complete grounding corpus the site's own assistant answers from: profile, production work, projects and skills in one plain-text document. Larger than llms.txt; use it when you need the detail in one request.",
        "tags": [
          "Discovery"
        ],
        "responses": {
          "200": {
            "description": "Get the full text corpus",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/feed.xml": {
      "get": {
        "operationId": "getFeed",
        "summary": "Get the RSS feed",
        "description": "An RSS feed of published articles and notes. Valid but empty when nothing is published.",
        "tags": [
          "Discovery"
        ],
        "responses": {
          "200": {
            "description": "Get the RSS feed",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/sitemap.xml": {
      "get": {
        "operationId": "getSitemap",
        "summary": "Get the XML sitemap",
        "description": "The pages the site submits to search engines, with change frequency and priority, in the sitemaps.org format.",
        "tags": [
          "Discovery"
        ],
        "responses": {
          "200": {
            "description": "Get the XML sitemap",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/robots.txt": {
      "get": {
        "operationId": "getRobotsTxt",
        "summary": "Get robots.txt and the AI content signal",
        "description": "Crawler rules and the Content-Signal preference (search=yes, ai-input=yes, ai-train=no), plus the sitemap location.",
        "tags": [
          "Discovery"
        ],
        "responses": {
          "200": {
            "description": "Get robots.txt and the AI content signal",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/openapi.json": {
      "get": {
        "operationId": "getOpenApiDocument",
        "summary": "Get this OpenAPI document",
        "description": "The machine-readable description of the API you are reading, in OpenAPI 3.1.",
        "tags": [
          "Discovery"
        ],
        "responses": {
          "200": {
            "description": "The OpenAPI document.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/mcp/mcp": {
      "post": {
        "operationId": "callMcpServer",
        "summary": "Call the portfolio MCP server",
        "description": "A read-only Model Context Protocol server over Streamable HTTP (JSON-RPC 2.0). Its tools return profile, projects, case studies, decisions, search and the résumé link; the tool table is at https://anvilry.vercel.app/mcp. Point an MCP client at this URL rather than calling it by hand. The legacy SSE transport is disabled and answers 404.",
        "tags": [
          "MCP"
        ],
        "requestBody": {
          "required": true,
          "description": "A JSON-RPC 2.0 request. Send `Accept: application/json, text/event-stream`.",
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/McpRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A JSON-RPC response, as JSON or as a server-sent event stream.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "default": {
            "$ref": "#/components/responses/McpError"
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "description": "The one error shape every JSON route uses. Branch on `code`, show `error` to a person.",
        "required": [
          "error",
          "code",
          "status",
          "hint",
          "docs"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message."
          },
          "code": {
            "type": "string",
            "enum": [
              "invalid_request",
              "not_found",
              "method_not_allowed",
              "payload_too_large",
              "rate_limited",
              "upstream_error",
              "not_configured"
            ],
            "description": "Stable machine-readable error code."
          },
          "status": {
            "type": "integer",
            "minimum": 400,
            "maximum": 599,
            "description": "The HTTP status, repeated in the body."
          },
          "hint": {
            "type": "string",
            "description": "What to try next."
          },
          "docs": {
            "type": "string",
            "format": "uri",
            "description": "Where the error codes are documented."
          }
        }
      },
      "ResumeJson": {
        "type": "object",
        "description": "A résumé in the JSON Resume format (jsonresume.org), built from the same content as the site. Education is omitted because no education data is published.",
        "required": [
          "basics",
          "work",
          "projects",
          "skills",
          "awards",
          "meta"
        ],
        "properties": {
          "$schema": {
            "type": "string",
            "format": "uri",
            "description": "The JSON Resume schema this document follows."
          },
          "basics": {
            "type": "object",
            "description": "Name, label, contact email, summary, location and public profiles.",
            "properties": {
              "name": {
                "type": "string"
              },
              "label": {
                "type": "string",
                "description": "Current role."
              },
              "email": {
                "type": "string",
                "format": "email"
              },
              "summary": {
                "type": "string"
              },
              "location": {
                "type": "object",
                "properties": {
                  "city": {
                    "type": "string"
                  },
                  "countryCode": {
                    "type": "string",
                    "description": "ISO 3166-1 alpha-2."
                  }
                }
              },
              "profiles": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "network": {
                      "type": "string"
                    },
                    "username": {
                      "type": "string"
                    },
                    "url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              },
              "url": {
                "type": "string",
                "format": "uri"
              }
            }
          },
          "work": {
            "type": "array",
            "description": "Production case studies.",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string",
                  "description": "Employer."
                },
                "position": {
                  "type": "string"
                },
                "summary": {
                  "type": "string"
                },
                "highlights": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "url": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "projects": {
            "type": "array",
            "description": "Open-source and private projects. A private repository has no public repo URL.",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "description": {
                  "type": "string"
                },
                "keywords": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                },
                "url": {
                  "type": "string",
                  "format": "uri"
                },
                "entity": {
                  "type": "string",
                  "format": "uri",
                  "description": "The project's page on this site."
                }
              }
            }
          },
          "skills": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "name": {
                  "type": "string"
                },
                "keywords": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "awards": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "title": {
                  "type": "string"
                },
                "summary": {
                  "type": "string"
                }
              }
            }
          },
          "meta": {
            "type": "object",
            "properties": {
              "canonical": {
                "type": "string",
                "format": "uri"
              },
              "version": {
                "type": "string"
              }
            }
          }
        }
      },
      "GithubStats": {
        "type": "object",
        "description": "Aggregate numbers across the owner's listed repositories, refreshed hourly. Fails open: if GitHub is unreachable the numbers are zero rather than an error.",
        "required": [
          "totalStars",
          "totalForks",
          "topLanguages",
          "mostRecentPush",
          "repoCount",
          "followers",
          "publicRepos"
        ],
        "properties": {
          "totalStars": {
            "type": "integer",
            "minimum": 0,
            "description": "Stars across the listed repositories."
          },
          "totalForks": {
            "type": "integer",
            "minimum": 0,
            "description": "Forks across the listed repositories."
          },
          "topLanguages": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "Primary languages, most used first."
          },
          "mostRecentPush": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "description": "Timestamp of the latest push to any listed repository; null when none is known."
          },
          "repoCount": {
            "type": "integer",
            "minimum": 0,
            "description": "Number of repositories aggregated."
          },
          "followers": {
            "type": "integer",
            "minimum": 0,
            "description": "GitHub followers of the owner account."
          },
          "publicRepos": {
            "type": "integer",
            "minimum": 0,
            "description": "Public repositories on the owner account."
          }
        }
      },
      "McpRequest": {
        "type": "object",
        "description": "A JSON-RPC 2.0 request as defined by the Model Context Protocol (Streamable HTTP transport).",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "string",
              "integer"
            ],
            "description": "Request id; omit for notifications."
          },
          "method": {
            "type": "string",
            "description": "For example initialize, tools/list or tools/call."
          },
          "params": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "McpResponse": {
        "type": "object",
        "description": "A JSON-RPC 2.0 response, either a result or a JSON-RPC error object.",
        "required": [
          "jsonrpc"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "string",
              "integer",
              "null"
            ]
          },
          "result": {
            "type": "object",
            "additionalProperties": true
          },
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "integer"
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      }
    },
    "responses": {
      "Error": {
        "description": "Any error from a JSON route that is not the MCP endpoint. The body is the shared error object.",
        "headers": {
          "Allow": {
            "description": "On a 405, the methods the route accepts.",
            "schema": {
              "type": "string"
            }
          },
          "Retry-After": {
            "description": "On a 429, seconds to wait before retrying.",
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "McpError": {
        "description": "A failed MCP call. A request the MCP protocol itself rejects answers with a JSON-RPC error object; an unknown path or an unsupported method answers with the shared error object.",
        "content": {
          "application/json": {
            "schema": {
              "oneOf": [
                {
                  "$ref": "#/components/schemas/McpResponse"
                },
                {
                  "$ref": "#/components/schemas/Error"
                }
              ]
            }
          }
        }
      }
    }
  }
}