{
  "openapi": "3.1.0",
  "info": {
    "title": "Kartik Kabadi site API",
    "summary": "Read-only JSON API for kartikkabadi.com",
    "description": "A small read-only API for agents and tools. It returns public profile and project data as JSON. No authentication, no writes. Rate limit: 120 requests per minute per IP.\n\nVersioning: the API is versioned in the URL path. Version 1 lives at /api/v1. Breaking changes ship as a new version path (/api/v2), and v1 stays available for at least 180 days after a deprecation notice. Deprecations are announced with Deprecation and Sunset response headers and on https://kartikkabadi.com/developers. The unversioned /api paths are aliases for v1.",
    "version": "1.0.0",
    "contact": {
      "name": "Kartik Kabadi",
      "url": "https://kartikkabadi.com/contact",
      "email": "kartik@kartikkabadi.com"
    },
    "license": {
      "name": "Data is free to use with attribution to https://kartikkabadi.com"
    }
  },
  "servers": [
    {
      "url": "https://kartikkabadi.com",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Meta",
      "description": "Service index and discovery."
    },
    {
      "name": "Profile",
      "description": "Identity and contact data."
    },
    {
      "name": "Projects",
      "description": "Open-source project data."
    }
  ],
  "paths": {
    "/api/v1": {
      "get": {
        "operationId": "getApiIndex",
        "tags": [
          "Meta"
        ],
        "summary": "API index",
        "description": "Lists the available endpoints and links to the OpenAPI spec, developer docs, and the full site data file. Start here when you need to discover what this API offers.",
        "parameters": [
          {
            "name": "fields",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of top-level fields to keep. Omit to get the full document. Repeated parameters combine. Unknown names return a 400. A sparse response contains only the named fields, so the schema's required list applies to the full document.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z_]+(,[a-z_]+)*$",
              "examples": [
                "name,version,endpoints"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The endpoint list and discovery links.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Discovery document for this API.",
                  "properties": {
                    "name": {
                      "type": "string",
                      "description": "API name.",
                      "examples": [
                        "Kartik Kabadi API"
                      ]
                    },
                    "description": {
                      "type": "string",
                      "description": "What the API is."
                    },
                    "version": {
                      "type": "string",
                      "description": "API version.",
                      "examples": [
                        "1.0.0"
                      ]
                    },
                    "docs": {
                      "type": "string",
                      "format": "uri",
                      "description": "Developer documentation."
                    },
                    "spec": {
                      "type": "string",
                      "format": "uri",
                      "description": "This OpenAPI document."
                    },
                    "data": {
                      "type": "string",
                      "format": "uri",
                      "description": "Full site dataset as a single JSON file."
                    },
                    "endpoints": {
                      "type": "array",
                      "description": "The available endpoints.",
                      "items": {
                        "type": "object",
                        "description": "One API endpoint.",
                        "properties": {
                          "method": {
                            "type": "string",
                            "description": "HTTP method.",
                            "examples": [
                              "GET"
                            ]
                          },
                          "path": {
                            "type": "string",
                            "description": "Path relative to the server.",
                            "examples": [
                              "/api/v1/profile"
                            ]
                          },
                          "description": {
                            "type": "string",
                            "description": "What the endpoint returns."
                          }
                        },
                        "required": [
                          "method",
                          "path",
                          "description"
                        ]
                      }
                    }
                  },
                  "required": [
                    "name",
                    "description",
                    "version",
                    "docs",
                    "spec",
                    "data",
                    "endpoints"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A query parameter is invalid. The error hint lists the valid values.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No endpoint matches the path. Check the endpoint list in this document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The API is read-only. Use GET or HEAD.",
            "headers": {
              "Allow": {
                "$ref": "#/components/headers/Allow"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The rate limit was exceeded. Wait for Retry-After seconds, then retry.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The API could not build the response. Retry shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/profile": {
      "get": {
        "operationId": "getProfile",
        "tags": [
          "Profile"
        ],
        "summary": "Profile",
        "description": "Returns Kartik Kabadi's name, role, description, location, email, and profile links. Use this to identify him or to cite the site without scraping HTML.",
        "parameters": [
          {
            "name": "fields",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of top-level fields to keep. Omit to get the full document. Repeated parameters combine. Unknown names return a 400. A sparse response contains only the named fields, so the schema's required list applies to the full document.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z_]+(,[a-z_]+)*$",
              "examples": [
                "name,role,links"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The profile.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Public identity data for Kartik Kabadi.",
                  "properties": {
                    "name": {
                      "type": "string",
                      "description": "Full name.",
                      "examples": [
                        "Kartik Kabadi"
                      ]
                    },
                    "role": {
                      "type": "string",
                      "description": "What he is currently building.",
                      "examples": [
                        "Building Vex"
                      ]
                    },
                    "description": {
                      "type": "string",
                      "description": "One-line summary."
                    },
                    "url": {
                      "type": "string",
                      "format": "uri",
                      "description": "Canonical site URL."
                    },
                    "email": {
                      "type": "string",
                      "format": "email",
                      "description": "Contact email."
                    },
                    "city": {
                      "type": "string",
                      "description": "City.",
                      "examples": [
                        "Bengaluru"
                      ]
                    },
                    "links": {
                      "type": "object",
                      "description": "Profile links.",
                      "properties": {
                        "vex": {
                          "type": "string",
                          "format": "uri",
                          "description": "Vex, the product he is building."
                        },
                        "synara": {
                          "type": "string",
                          "format": "uri",
                          "description": "Synara, a project he contributes to."
                        },
                        "x": {
                          "type": "string",
                          "format": "uri",
                          "description": "X profile."
                        },
                        "github": {
                          "type": "string",
                          "format": "uri",
                          "description": "GitHub profile."
                        },
                        "youtube": {
                          "type": "string",
                          "format": "uri",
                          "description": "YouTube channel."
                        },
                        "linkedin": {
                          "type": "string",
                          "format": "uri",
                          "description": "LinkedIn profile."
                        }
                      },
                      "required": [
                        "vex",
                        "synara",
                        "x",
                        "github",
                        "youtube",
                        "linkedin"
                      ]
                    },
                    "updated": {
                      "type": "string",
                      "format": "date-time",
                      "description": "When the underlying data was last refreshed."
                    }
                  },
                  "required": [
                    "name",
                    "role",
                    "description",
                    "url",
                    "email",
                    "city",
                    "links",
                    "updated"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A query parameter is invalid. The error hint lists the valid values.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No endpoint matches the path. Check the endpoint list in this document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The API is read-only. Use GET or HEAD.",
            "headers": {
              "Allow": {
                "$ref": "#/components/headers/Allow"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The rate limit was exceeded. Wait for Retry-After seconds, then retry.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The API could not build the response. Retry shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/projects": {
      "get": {
        "operationId": "getProjects",
        "tags": [
          "Projects"
        ],
        "summary": "Projects",
        "description": "Returns the open-source projects shown on the home page, with a description, a link, and the current GitHub star count for each. Page through the list with limit and cursor.",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum number of projects to return, between 1 and 50. Omit to get every project in one page.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Cursor from the next_cursor field of the previous page. Omit for the first page.",
            "schema": {
              "type": "string",
              "pattern": "^[0-9]+$"
            }
          },
          {
            "name": "fields",
            "in": "query",
            "required": false,
            "description": "Comma-separated list of top-level fields to keep. Omit to get the full document. Repeated parameters combine. Unknown names return a 400. A sparse response contains only the named fields, so the schema's required list applies to the full document.",
            "schema": {
              "type": "string",
              "pattern": "^[a-z_]+(,[a-z_]+)*$",
              "examples": [
                "count,projects"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One page of the project list.",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              },
              "RateLimit-Policy": {
                "$ref": "#/components/headers/RateLimit-Policy"
              },
              "Deprecation": {
                "$ref": "#/components/headers/Deprecation"
              },
              "Sunset": {
                "$ref": "#/components/headers/Sunset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "One page of the project list.",
                  "properties": {
                    "count": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Number of projects in this page."
                    },
                    "total": {
                      "type": "integer",
                      "minimum": 0,
                      "description": "Total number of projects."
                    },
                    "next_cursor": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "description": "Pass this as the cursor parameter to get the next page; null on the last page.",
                      "examples": [
                        "15"
                      ]
                    },
                    "updated": {
                      "type": "string",
                      "format": "date-time",
                      "description": "When the star counts were last refreshed."
                    },
                    "projects": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "description": "One open-source project.",
                        "properties": {
                          "name": {
                            "type": "string",
                            "description": "Project name.",
                            "examples": [
                              "colony"
                            ]
                          },
                          "description": {
                            "type": "string",
                            "description": "One-line description."
                          },
                          "url": {
                            "type": "string",
                            "format": "uri",
                            "description": "Home page or repository."
                          },
                          "stars": {
                            "type": "integer",
                            "minimum": 0,
                            "description": "GitHub stars at the last data refresh."
                          }
                        },
                        "required": [
                          "name",
                          "description",
                          "url",
                          "stars"
                        ]
                      }
                    }
                  },
                  "required": [
                    "count",
                    "total",
                    "next_cursor",
                    "updated",
                    "projects"
                  ]
                }
              }
            }
          },
          "400": {
            "description": "A query parameter is invalid. The error hint lists the valid values.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No endpoint matches the path. Check the endpoint list in this document.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "405": {
            "description": "The API is read-only. Use GET or HEAD.",
            "headers": {
              "Allow": {
                "$ref": "#/components/headers/Allow"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "The rate limit was exceeded. Wait for Retry-After seconds, then retry.",
            "headers": {
              "Retry-After": {
                "$ref": "#/components/headers/Retry-After"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimit-Limit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimit-Remaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimit-Reset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "The API could not build the response. Retry shortly.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "headers": {
      "RateLimit-Limit": {
        "description": "Requests allowed per window.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "RateLimit-Remaining": {
        "description": "Requests left in the current window.",
        "schema": {
          "type": "integer",
          "minimum": 0
        }
      },
      "RateLimit-Reset": {
        "description": "Seconds until the window resets.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "RateLimit-Policy": {
        "description": "The quota policy, in the form limit;w=window-seconds.",
        "schema": {
          "type": "string",
          "examples": [
            "120;w=60"
          ]
        }
      },
      "Retry-After": {
        "description": "Seconds to wait before retrying after a 429.",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "Allow": {
        "description": "Methods allowed on the endpoint.",
        "schema": {
          "type": "string",
          "examples": [
            "GET, HEAD, OPTIONS"
          ]
        }
      },
      "Deprecation": {
        "description": "RFC 9745 deprecation signal. Sent only on deprecated endpoints; nothing is deprecated today.",
        "schema": {
          "type": "string",
          "examples": [
            "@1740787200"
          ]
        }
      },
      "Sunset": {
        "description": "RFC 8594 removal date. Sent only on deprecated endpoints; nothing is deprecated today.",
        "schema": {
          "type": "string",
          "examples": [
            "Wed, 30 Sep 2026 23:59:59 GMT"
          ]
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "description": "JSON error envelope. Every error response uses this shape.",
        "properties": {
          "error": {
            "type": "object",
            "properties": {
              "code": {
                "type": "string",
                "description": "Stable machine-readable code.",
                "enum": [
                  "invalid_parameter",
                  "not_found",
                  "method_not_allowed",
                  "rate_limited",
                  "upstream_unavailable"
                ]
              },
              "message": {
                "type": "string",
                "description": "What went wrong."
              },
              "hint": {
                "type": "string",
                "description": "How to fix the request."
              },
              "docs": {
                "type": "string",
                "format": "uri",
                "description": "Where to read more."
              }
            },
            "required": [
              "code",
              "message",
              "hint",
              "docs"
            ]
          }
        },
        "required": [
          "error"
        ]
      }
    }
  }
}
