{
  "openapi": "3.1.0",
  "info": {
    "title": "Ruben Marcus Portfolio API",
    "version": "1.0.0",
    "description": "Public machine-readable access to Ruben Marcus's portfolio and MCP server. All endpoints answer typed JSON errors, accept CORS from any origin, and advertise rate limits via RateLimit headers.",
    "contact": { "name": "Ruben Marcus", "url": "https://www.rubenmarcus.dev/contact" },
    "license": { "name": "MIT", "url": "https://github.com/rubenmarcus/portfolio/blob/main/LICENSE" }
  },
  "servers": [{ "url": "https://www.rubenmarcus.dev" }],
  "components": {
    "schemas": {
      "Error": {
        "type": "object",
        "required": ["ok", "error"],
        "properties": {
          "ok": { "type": "boolean", "enum": [false] },
          "error": { "type": "string", "description": "Human-readable error message" },
          "leadId": { "type": "string", "description": "Present on hire failures when the lead was still captured" }
        }
      },
      "JsonRpcError": {
        "type": "object",
        "required": ["jsonrpc", "error", "id"],
        "properties": {
          "jsonrpc": { "type": "string", "enum": ["2.0"] },
          "error": {
            "type": "object",
            "required": ["code", "message"],
            "properties": {
              "code": { "type": "integer" },
              "message": { "type": "string" },
              "data": { "type": "object" }
            }
          },
          "id": { "type": ["string", "number", "null"] }
        }
      },
      "Health": {
        "type": "object",
        "required": ["ok", "service", "time"],
        "properties": {
          "ok": { "type": "boolean" },
          "service": { "type": "string" },
          "version": { "type": "string" },
          "time": { "type": "string", "format": "date-time" },
          "supabase": { "type": "boolean" }
        }
      },
      "HireRequest": {
        "type": "object",
        "required": ["name", "contact", "brief"],
        "properties": {
          "name": { "type": "string", "maxLength": 120 },
          "contact": { "type": "string", "maxLength": 160, "description": "Email or Telegram handle" },
          "brief": { "type": "string", "maxLength": 4000 },
          "budget": { "type": "string", "maxLength": 120 },
          "agent": { "type": "string", "maxLength": 80, "description": "chatgpt | claude | kimi | other" },
          "attribution": { "type": "object", "description": "Optional attribution keys: source, utm_*, referrer, landing" }
        }
      },
      "HireResponse": {
        "type": "object",
        "required": ["ok", "leadId", "message"],
        "properties": {
          "ok": { "type": "boolean" },
          "leadId": { "type": "string", "description": "Stable id; retries with the same Idempotency-Key replay this value" },
          "message": { "type": "string" },
          "calendar": { "type": "string", "format": "uri" }
        }
      },
      "Counter": {
        "type": "object",
        "required": ["ok", "slug", "count"],
        "properties": {
          "ok": { "type": "boolean" },
          "slug": { "type": "string" },
          "count": { "type": "integer" }
        }
      },
      "SubscribeRequest": {
        "type": "object",
        "required": ["email"],
        "properties": {
          "email": { "type": "string", "format": "email" },
          "lang": { "type": "string", "maxLength": 8 },
          "source": { "type": "string", "maxLength": 80 }
        }
      },
      "OkEnvelope": {
        "type": "object",
        "required": ["ok"],
        "properties": { "ok": { "type": "boolean" } }
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "schema": { "type": "string", "minLength": 8, "maxLength": 128 },
        "description": "Unique key; retries with the same key replay the original answer without re-delivering."
      },
      "SlugQuery": {
        "name": "slug",
        "in": "query",
        "required": true,
        "schema": { "type": "string" },
        "description": "Post slug or lab demo slug"
      }
    },
    "responses": {
      "RateLimited": {
        "description": "Rate limit exceeded",
        "headers": {
          "RateLimit": { "schema": { "type": "string" } },
          "RateLimit-Policy": { "schema": { "type": "string" } },
          "Retry-After": { "schema": { "type": "integer" } }
        },
        "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
      }
    }
  },
  "paths": {
    "/api/mcp": {
      "get": {
        "operationId": "discoverMcp",
        "summary": "Discover the MCP endpoint",
        "responses": {
          "200": {
            "description": "MCP server discovery document",
            "content": { "application/json": { "schema": { "type": "object" } } }
          }
        }
      },
      "post": {
        "operationId": "callMcp",
        "summary": "Send a JSON-RPC request to the public MCP server",
        "description": "Streamable HTTP MCP transport. Start with initialize, then tools/list and tools/call.",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["jsonrpc", "method"], "properties": { "jsonrpc": { "type": "string", "enum": ["2.0"] }, "id": {}, "method": { "type": "string" }, "params": { "type": "object" } } } } }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response",
            "content": { "application/json": { "schema": { "type": "object" } } }
          },
          "400": {
            "description": "JSON-RPC error (-32700 parse, -32601 method not found, -32602 invalid params)",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/JsonRpcError" } } }
          }
        }
      }
    },
    "/api/resume.json": {
      "get": {
        "operationId": "getResumeJson",
        "summary": "Get the resume as structured JSON",
        "responses": {
          "200": {
            "description": "Structured resume",
            "content": { "application/json": { "schema": { "type": "object" } } }
          }
        }
      }
    },
    "/api/resume.txt": {
      "get": {
        "operationId": "getResumeText",
        "summary": "Get the resume as plain text",
        "responses": {
          "200": {
            "description": "Plain-text resume",
            "content": { "text/plain": { "schema": { "type": "string" } } }
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Check API health",
        "responses": {
          "200": {
            "description": "Service is operational",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Health" } } }
          }
        }
      }
    },
    "/api/stats": {
      "get": {
        "operationId": "getStats",
        "summary": "Public telemetry: views, likes, MCP usage, leads",
        "responses": {
          "200": {
            "description": "Aggregated observability counters",
            "content": { "application/json": { "schema": { "type": "object" } } }
          },
          "503": {
            "description": "Store unavailable",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/api/github-stats.json": {
      "get": {
        "operationId": "getGithubStats",
        "summary": "GitHub stars and npm downloads snapshot",
        "responses": {
          "200": {
            "description": "Cached GitHub/npm counters",
            "content": { "application/json": { "schema": { "type": "object" } } }
          }
        }
      }
    },
    "/api/views": {
      "get": {
        "operationId": "getViews",
        "summary": "Read a post's view counter",
        "parameters": [{ "$ref": "#/components/parameters/SlugQuery" }],
        "responses": {
          "200": {
            "description": "Current view count",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Counter" } } }
          },
          "400": {
            "description": "Missing slug",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      },
      "post": {
        "operationId": "incrementViews",
        "summary": "Increment a post's view counter",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["slug"], "properties": { "slug": { "type": "string" } } } } }
        },
        "responses": {
          "200": {
            "description": "New view count",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Counter" } } }
          }
        }
      }
    },
    "/api/likes": {
      "get": {
        "operationId": "getLikes",
        "summary": "Read a post's like counter",
        "parameters": [{ "$ref": "#/components/parameters/SlugQuery" }],
        "responses": {
          "200": {
            "description": "Current like count",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Counter" } } }
          }
        }
      },
      "post": {
        "operationId": "incrementLikes",
        "summary": "Increment a post's like counter",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "type": "object", "required": ["slug"], "properties": { "slug": { "type": "string" } } } } }
        },
        "responses": {
          "200": {
            "description": "New like count",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Counter" } } }
          }
        }
      }
    },
    "/api/subscribe": {
      "post": {
        "operationId": "subscribe",
        "summary": "Join the newsletter list",
        "description": "Duplicate emails are silently accepted (on-conflict dedup).",
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SubscribeRequest" } } }
        },
        "responses": {
          "200": {
            "description": "Subscribed (or already on the list)",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/OkEnvelope" } } }
          },
          "400": {
            "description": "Invalid email",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "503": {
            "description": "Store unavailable",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    },
    "/api/hire": {
      "get": {
        "operationId": "describeHire",
        "summary": "Describe the hire contract (agent probes)",
        "responses": {
          "200": {
            "description": "Self-documenting contract",
            "content": { "application/json": { "schema": { "type": "object" } } }
          }
        }
      },
      "post": {
        "operationId": "submitBrief",
        "summary": "Submit a project brief",
        "description": "Delivers a project inquiry through private lead destinations. Send only after the person you are acting for has explicitly confirmed. Supports the Idempotency-Key header.",
        "parameters": [{ "$ref": "#/components/parameters/IdempotencyKey" }],
        "requestBody": {
          "required": true,
          "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HireRequest" } } }
        },
        "responses": {
          "200": {
            "description": "Lead accepted and delivered",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/HireResponse" } } }
          },
          "400": {
            "description": "Invalid request",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          },
          "502": {
            "description": "No lead destination accepted the request (leadId still returned)",
            "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Error" } } }
          }
        }
      }
    }
  }
}
