{
  "openapi": "3.1.0",
  "info": {
    "title": "Sam Carlton Public Agent API",
    "version": "2026-04-27",
    "description": "Public discovery endpoints for reading samcarlton.com articles, exploring portfolio projects, submitting reviewed contact form inquiries, and discovering the site's MCP tools."
  },
  "servers": [
    {
      "url": "https://samcarlton.com",
      "description": "Production"
    }
  ],
  "paths": {
    "/api/articles.json": {
      "get": {
        "summary": "List public articles",
        "description": "Returns public article metadata with canonical page URLs, Markdown alternate URLs, and per-article JSON URLs.",
        "operationId": "listArticles",
        "responses": {
          "200": {
            "description": "Public article list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ArticleList"
                }
              }
            }
          }
        }
      }
    },
    "/api/articles/{slug}.json": {
      "get": {
        "summary": "Read one public article",
        "description": "Returns one public article by slug, including the source MDX body.",
        "operationId": "readArticle",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "how-i-like-to-keep-ai-honest"
          }
        ],
        "responses": {
          "200": {
            "description": "Public article payload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Article"
                }
              }
            }
          }
        }
      }
    },
    "/api/projects.json": {
      "get": {
        "summary": "List public projects",
        "description": "Returns public project and portfolio metadata with canonical page URLs, Markdown alternate URLs, and per-project JSON URLs.",
        "operationId": "listProjects",
        "responses": {
          "200": {
            "description": "Public project list.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectList"
                }
              }
            }
          }
        }
      }
    },
    "/api/projects/{slug}.json": {
      "get": {
        "summary": "Read one public project",
        "description": "Returns one public project or portfolio item by slug, including the source MDX body.",
        "operationId": "readProject",
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            },
            "example": "church-on-the-move-messages-platform"
          }
        ],
        "responses": {
          "200": {
            "description": "Public project payload.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Project"
                }
              }
            }
          }
        }
      }
    },
    "/api/contact": {
      "post": {
        "summary": "Submit the public contact form",
        "description": "Accepts the same multipart form fields used by the public contact page. Submissions are protected by reCAPTCHA and forwarded to contact@samcarlton.com by email. Automated clients should only submit after user review and with a valid client-side reCAPTCHA token.",
        "operationId": "submitContactForm",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/ContactFormSubmission"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/ContactFormSubmission"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Submission accepted and email forwarding succeeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ContactSuccess"
                }
              }
            }
          },
          "400": {
            "description": "Required fields, email validation, form parsing, or reCAPTCHA validation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "405": {
            "description": "Method is not allowed for this endpoint.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "500": {
            "description": "Server configuration is missing.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          },
          "502": {
            "description": "Email delivery failed after validation succeeded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      },
      "options": {
        "summary": "CORS preflight for the contact form",
        "operationId": "contactFormPreflight",
        "responses": {
          "204": {
            "description": "Preflight accepted."
          }
        }
      }
    },
    "/mcp": {
      "get": {
        "summary": "Describe the MCP server",
        "description": "Returns a compact JSON description of the site's Streamable HTTP MCP endpoint and available tools.",
        "operationId": "getMcpSummary",
        "responses": {
          "200": {
            "description": "MCP endpoint summary.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpSummary"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Call the MCP JSON-RPC endpoint",
        "description": "Supports initialize, ping, tools/list, and tools/call for article reading, project exploration, and guarded contact form submission.",
        "operationId": "callMcp",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/McpJsonRpcRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "MCP JSON-RPC response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpJsonRpcResponse"
                }
              }
            }
          },
          "400": {
            "description": "Invalid JSON-RPC request.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/McpJsonRpcResponse"
                }
              }
            }
          }
        }
      },
      "options": {
        "summary": "CORS preflight for the MCP endpoint",
        "operationId": "mcpPreflight",
        "responses": {
          "204": {
            "description": "Preflight accepted."
          }
        }
      }
    },
    "/api/health": {
      "get": {
        "summary": "Check public API health",
        "description": "Returns a small JSON health response for API catalog status discovery.",
        "operationId": "getApiHealth",
        "responses": {
          "200": {
            "description": "The public API surface is reachable.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          }
        }
      },
      "head": {
        "summary": "Check public API health headers",
        "operationId": "headApiHealth",
        "responses": {
          "200": {
            "description": "The public API surface is reachable."
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "AgentContentSummary": {
        "type": "object",
        "required": [
          "type",
          "slug",
          "title",
          "url",
          "apiUrl",
          "markdownUrl"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "article",
              "project"
            ]
          },
          "slug": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "url": {
            "type": "string",
            "format": "uri"
          },
          "apiUrl": {
            "type": "string",
            "format": "uri"
          },
          "markdownUrl": {
            "type": "string",
            "format": "uri"
          },
          "publishDate": {
            "type": [
              "string",
              "null"
            ]
          },
          "categories": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "sourceUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "externalUrl": {
            "type": [
              "string",
              "null"
            ],
            "format": "uri"
          },
          "image": {
            "type": [
              "string",
              "null"
            ]
          }
        },
        "additionalProperties": false
      },
      "ArticleList": {
        "type": "object",
        "required": [
          "type",
          "count",
          "items"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "article-list"
          },
          "count": {
            "type": "integer",
            "minimum": 0
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AgentContentSummary"
            }
          }
        },
        "additionalProperties": false
      },
      "Article": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AgentContentSummary"
          },
          {
            "type": "object",
            "required": [
              "body"
            ],
            "properties": {
              "body": {
                "type": "string",
                "description": "The source MDX body for the article."
              }
            }
          }
        ]
      },
      "ProjectList": {
        "type": "object",
        "required": [
          "type",
          "count",
          "items"
        ],
        "properties": {
          "type": {
            "type": "string",
            "const": "project-list"
          },
          "count": {
            "type": "integer",
            "minimum": 0
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AgentContentSummary"
            }
          }
        },
        "additionalProperties": false
      },
      "Project": {
        "allOf": [
          {
            "$ref": "#/components/schemas/AgentContentSummary"
          },
          {
            "type": "object",
            "required": [
              "body"
            ],
            "properties": {
              "body": {
                "type": "string",
                "description": "The source MDX body for the project or portfolio item."
              }
            }
          }
        ]
      },
      "ContactFormSubmission": {
        "type": "object",
        "required": [
          "name",
          "email",
          "message",
          "g-recaptcha-response"
        ],
        "properties": {
          "source": {
            "type": "string",
            "maxLength": 120,
            "description": "Optional route, offer, or campaign context for the inquiry."
          },
          "name": {
            "type": "string",
            "description": "Sender name."
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Sender email address used as the Reply-To value."
          },
          "message": {
            "type": "string",
            "description": "Project, workflow, prototype, or technical problem details."
          },
          "g-recaptcha-response": {
            "type": "string",
            "description": "Client-side reCAPTCHA token generated by the contact form."
          },
          "page_url": {
            "type": "string",
            "maxLength": 500,
            "description": "Page URL where the inquiry started."
          },
          "referrer_url": {
            "type": "string",
            "maxLength": 500,
            "description": "Referrer URL captured by the browser or agent."
          },
          "utm_source": {
            "type": "string",
            "maxLength": 120
          },
          "utm_medium": {
            "type": "string",
            "maxLength": 120
          },
          "utm_campaign": {
            "type": "string",
            "maxLength": 160
          },
          "utm_content": {
            "type": "string",
            "maxLength": 160
          },
          "utm_term": {
            "type": "string",
            "maxLength": 160
          },
          "self_reported_source": {
            "type": "string",
            "maxLength": 500
          },
          "referral_person_or_org": {
            "type": "string",
            "maxLength": 240
          },
          "company": {
            "type": "string",
            "maxLength": 240
          },
          "website": {
            "type": "string",
            "maxLength": 500
          },
          "project_type": {
            "type": "string",
            "maxLength": 160
          },
          "budget_range": {
            "type": "string",
            "maxLength": 160
          },
          "timeline": {
            "type": "string",
            "maxLength": 240
          },
          "preferred_contact_method": {
            "type": "string",
            "maxLength": 160
          },
          "phone": {
            "type": "string",
            "maxLength": 80
          },
          "asset_urls": {
            "type": "string",
            "maxLength": 2000,
            "description": "Relevant repo, screenshot, Loom, PRD, doc, workflow, or example URLs."
          }
        },
        "additionalProperties": false
      },
      "ContactSuccess": {
        "type": "object",
        "required": [
          "ok"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          }
        },
        "additionalProperties": false
      },
      "McpSummary": {
        "type": "object",
        "required": [
          "name",
          "version",
          "protocolVersion",
          "endpoint",
          "capabilities",
          "tools"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "protocolVersion": {
            "type": "string"
          },
          "endpoint": {
            "type": "string",
            "format": "uri"
          },
          "capabilities": {
            "type": "object"
          },
          "tools": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/McpToolSummary"
            }
          }
        },
        "additionalProperties": true
      },
      "McpToolSummary": {
        "type": "object",
        "required": [
          "name",
          "description"
        ],
        "properties": {
          "name": {
            "type": "string"
          },
          "title": {
            "type": "string"
          },
          "description": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "McpJsonRpcRequest": {
        "type": "object",
        "required": [
          "jsonrpc",
          "method"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "string",
              "integer",
              "null"
            ]
          },
          "method": {
            "type": "string",
            "enum": [
              "initialize",
              "ping",
              "tools/list",
              "tools/call"
            ]
          },
          "params": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "McpJsonRpcResponse": {
        "type": "object",
        "required": [
          "jsonrpc"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "string",
              "integer",
              "null"
            ]
          },
          "result": {
            "type": "object",
            "additionalProperties": true
          },
          "error": {
            "$ref": "#/components/schemas/McpError"
          }
        },
        "additionalProperties": true
      },
      "McpError": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "integer"
          },
          "message": {
            "type": "string"
          },
          "data": {}
        },
        "additionalProperties": true
      },
      "ErrorResponse": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string"
          },
          "detail": {
            "type": "string"
          },
          "missing": {
            "type": "string"
          },
          "score": {
            "type": "number"
          },
          "codes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        },
        "additionalProperties": true
      },
      "HealthResponse": {
        "type": "object",
        "required": [
          "ok",
          "service",
          "version"
        ],
        "properties": {
          "ok": {
            "type": "boolean",
            "const": true
          },
          "service": {
            "type": "string",
            "const": "samcarlton-contact-api"
          },
          "version": {
            "type": "string"
          }
        },
        "additionalProperties": false
      }
    }
  }
}
