{
  "openapi": "3.1.0",
  "info": {
    "title": "bshp.io public read API",
    "version": "1.0.0",
    "description": "Read-only JSON for Ben Bishop's personal portfolio (projects, articles, stack). No authentication. This is not a multi-tenant product API.\n\nDocumented paths are the canonical extensionless URLs. At build time the site emits matching static files with a .json suffix (for example /api/v1/projects.json and /api/v1/projects/{slug}.json). On Cloudflare Pages, public/_redirects rewrites extensionless aliases to those files. Prefer *.json when debugging outside Cloudflare (local preview, other static hosts).\n\nPOST /api/contact is a human contact form handler. It is origin-restricted and rate-limited and is not part of this public read API.",
    "contact": {
      "name": "Ben Bishop",
      "url": "https://bshp.io/contact"
    }
  },
  "servers": [
    {
      "url": "https://bshp.io",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Discovery",
      "description": "API index and related machine surfaces"
    },
    {
      "name": "Projects",
      "description": "Portfolio projects from content collections"
    },
    {
      "name": "Articles",
      "description": "Published articles only; drafts are never included"
    },
    {
      "name": "Stack",
      "description": "Models, tools, and current focus"
    }
  ],
  "paths": {
    "/api/v1": {
      "get": {
        "tags": [
          "Discovery"
        ],
        "summary": "API index",
        "description": "Lists public endpoints, human page URLs, and notes on the contact form.",
        "operationId": "getApiIndex",
        "responses": {
          "200": {
            "description": "Endpoint index",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/projects": {
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "List projects",
        "description": "Metadata for all projects (no markdown body). Sorted newest first.",
        "operationId": "listProjects",
        "responses": {
          "200": {
            "description": "Project list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "projects": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ProjectListItem"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/projects/{slug}": {
      "get": {
        "tags": [
          "Projects"
        ],
        "summary": "Get project by slug",
        "description": "Full project record including markdown body and case study.",
        "operationId": "getProject",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Project content collection id (filename without extension)"
          }
        ],
        "responses": {
          "200": {
            "description": "Project detail",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectDetail"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/articles": {
      "get": {
        "tags": [
          "Articles"
        ],
        "summary": "List published articles",
        "description": "Metadata only. Drafts are excluded.",
        "operationId": "listArticles",
        "responses": {
          "200": {
            "description": "Article list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "articles": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ArticleListItem"
                      }
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/articles/{slug}": {
      "get": {
        "tags": [
          "Articles"
        ],
        "summary": "Get published article by slug",
        "description": "Full article including markdown body. Drafts are not built as routes.",
        "operationId": "getArticle",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "Article content collection id (filename without extension)"
          }
        ],
        "responses": {
          "200": {
            "description": "Article detail",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArticleDetail"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/stack": {
      "get": {
        "tags": [
          "Stack"
        ],
        "summary": "Get tech stack profile",
        "description": "Models, daily drivers, and current focus.",
        "operationId": "getStack",
        "responses": {
          "200": {
            "description": "Stack profile",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Stack"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ProjectListItem": {
        "type": "object",
        "required": [
          "slug",
          "title",
          "tagline",
          "description",
          "github",
          "status",
          "stack",
          "featured",
          "date",
          "url",
          "apiUrl"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "tagline": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "github": {
            "type": "string",
            "format": "uri"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "reference",
              "experimental"
            ]
          },
          "stack": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "featured": {
            "type": "boolean"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "apiUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "ProjectDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ProjectListItem"
          },
          {
            "type": "object",
            "required": [
              "body",
              "caseStudy"
            ],
            "properties": {
              "body": {
                "type": "string",
                "description": "Raw markdown body from the content collection"
              },
              "caseStudy": {
                "type": "object",
                "additionalProperties": {
                  "type": "string"
                }
              }
            }
          }
        ]
      },
      "ArticleListItem": {
        "type": "object",
        "required": [
          "slug",
          "title",
          "description",
          "date",
          "tags",
          "url",
          "apiUrl"
        ],
        "properties": {
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "date": {
            "type": "string",
            "format": "date"
          },
          "tags": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "apiUrl": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "ArticleDetail": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ArticleListItem"
          },
          {
            "type": "object",
            "required": [
              "body"
            ],
            "properties": {
              "body": {
                "type": "string",
                "description": "Raw markdown body from the content collection"
              }
            }
          }
        ]
      },
      "Stack": {
        "type": "object",
        "required": [
          "lastUpdated",
          "currentFocus",
          "models",
          "dailyDrivers",
          "url"
        ],
        "properties": {
          "lastUpdated": {
            "type": "string",
            "format": "date"
          },
          "currentFocus": {
            "type": "string"
          },
          "models": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "name",
                "type",
                "description"
              ],
              "properties": {
                "name": {
                  "type": "string"
                },
                "type": {
                  "type": "string",
                  "enum": [
                    "subscription",
                    "local"
                  ]
                },
                "description": {
                  "type": "string"
                },
                "url": {
                  "type": "string",
                  "format": "uri"
                }
              }
            }
          },
          "dailyDrivers": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "tool",
                "role",
                "status"
              ],
              "properties": {
                "tool": {
                  "type": "string"
                },
                "role": {
                  "type": "string"
                },
                "url": {
                  "type": "string",
                  "format": "uri"
                },
                "status": {
                  "type": "string",
                  "enum": [
                    "daily",
                    "exploring"
                  ]
                }
              }
            }
          },
          "url": {
            "type": "string",
            "format": "uri"
          }
        }
      }
    }
  },
  "x-contact-form": {
    "path": "/api/contact",
    "method": "POST",
    "note": "Human contact form only. Origin allowlist and rate limits apply. Not a public integration endpoint. Not documented as an OpenAPI operation for that reason."
  }
}