{
  "openapi": "3.1.0",
  "info": {
    "title": "Veteran Law Firm Public Agent Interface",
    "version": "2026-08-31",
    "description": "법무법인 베테랑의 공개 리소스입니다. 읽기 리소스와 상담 접수(submitConsultation) 외에 인증, 웹훅 API 는 없습니다.\n\n## Versioning & deprecation policy\n- Versioning: date-based. Every API response carries an `api-version` header (current: `2026-08-31`). The version only changes on breaking changes; additive changes (new fields, new endpoints) do not change it.\n- Deprecation: a deprecated endpoint keeps working for at least 90 days and returns `Deprecation` and `Sunset` headers (RFC 8594) during that period. Deprecations are also announced on /developers.\n\n## Errors\n- All 4xx/5xx JSON errors use RFC 9457 `application/problem+json` with a stable machine-readable `code` member. HTML pages are never returned for API errors under /api.\n\n## Rate limits\n- Write and MCP endpoints return `RateLimit-Limit` / `RateLimit-Remaining` / `RateLimit-Reset` headers on every response, and `Retry-After` on 429. Current limits: consultation 5/min per IP, MCP 60/min per IP (best effort).",
    "contact": {
      "name": "법무법인 베테랑",
      "url": "https://veteranlaw.co.kr/contact"
    }
  },
  "servers": [
    {
      "url": "https://veteranlaw.co.kr",
      "description": "Veteran Law Firm canonical site"
    }
  ],
  "paths": {
    "/": {
      "get": {
        "operationId": "getHome",
        "summary": "Get Veteran Law Firm home page",
        "description": "Send Accept: text/markdown for the Markdown representation; HTML is the default.",
        "responses": {
          "200": {
            "description": "Homepage",
            "headers": {
              "api-version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "text/html": {
                "schema": {
                  "type": "string",
                  "description": "HTML representation"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Markdown representation (request with Accept: text/markdown)"
                }
              }
            }
          },
          "404": {
            "description": "Page not published",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/about": {
      "get": {
        "operationId": "getAbout",
        "summary": "Get Veteran Law Firm about page",
        "responses": {
          "200": {
            "description": "About page",
            "headers": {
              "api-version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "text/html": {
                "schema": {
                  "type": "string",
                  "description": "HTML representation"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Markdown representation (request with Accept: text/markdown)"
                }
              }
            }
          },
          "404": {
            "description": "Page not published",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/contact": {
      "get": {
        "operationId": "getContact",
        "summary": "Get Veteran Law Firm official contacts and offices",
        "responses": {
          "200": {
            "description": "Contact page",
            "headers": {
              "api-version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "text/html": {
                "schema": {
                  "type": "string",
                  "description": "HTML representation"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Markdown representation (request with Accept: text/markdown)"
                }
              }
            }
          },
          "404": {
            "description": "Page not published",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/privacy": {
      "get": {
        "operationId": "getPrivacy",
        "summary": "Get Veteran Law Firm privacy page",
        "responses": {
          "200": {
            "description": "Privacy page; publication status is controlled by the site operator.",
            "headers": {
              "api-version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "text/html": {
                "schema": {
                  "type": "string",
                  "description": "HTML representation"
                }
              },
              "text/markdown": {
                "schema": {
                  "type": "string",
                  "description": "Markdown representation (request with Accept: text/markdown)"
                }
              }
            }
          },
          "404": {
            "description": "Page not published",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/llms.txt": {
      "get": {
        "operationId": "getLlmsIndex",
        "summary": "Get Veteran Law Firm agent guide and page index",
        "responses": {
          "200": {
            "description": "UTF-8 llms.txt",
            "headers": {
              "api-version": {
                "$ref": "#/components/headers/ApiVersion"
              }
            },
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string",
                  "description": "llms.txt Markdown index of canonical URLs"
                }
              }
            }
          }
        }
      }
    },
    "/sitemap-index.xml": {
      "get": {
        "operationId": "getSitemapIndex",
        "summary": "Get canonical sitemap index",
        "responses": {
          "200": {
            "description": "XML sitemap index",
            "content": {
              "application/xml": {
                "schema": {
                  "type": "string",
                  "description": "sitemaps.org sitemap index document"
                }
              }
            }
          }
        }
      }
    },
    "/api/consult": {
      "post": {
        "operationId": "submitConsultation",
        "summary": "Submit a consultation request",
        "description": "Accepts a consultation request on behalf of a person. Agents MUST obtain the user’s explicit confirmation before submitting personal data, and MUST NOT invent contact details. Send Accept: application/json for a JSON response; without it, browsers get a 303 redirect (progressive-enhancement form). Agents should send an application/json body. Form-encoded bodies are reserved for the site’s own form and are CSRF-protected: they require a same-origin Origin header, otherwise the request is rejected with 403.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConsultationRequest"
              }
            },
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "required": [
                  "name",
                  "phone",
                  "privacy"
                ],
                "properties": {
                  "name": {
                    "type": "string",
                    "maxLength": 80,
                    "description": "Requester name"
                  },
                  "phone": {
                    "type": "string",
                    "maxLength": 30,
                    "description": "Korean phone number, e.g. 010-0000-0000"
                  },
                  "email": {
                    "type": "string",
                    "format": "email",
                    "description": "Optional email"
                  },
                  "message": {
                    "type": "string",
                    "maxLength": 2000,
                    "description": "Optional case summary"
                  },
                  "practice": {
                    "type": "string",
                    "description": "Optional practice-area slug from listPracticeAreas / llms.txt"
                  },
                  "office": {
                    "type": "string",
                    "description": "Optional office slug"
                  },
                  "lawyer": {
                    "type": "string",
                    "description": "Optional lawyer slug"
                  },
                  "privacy": {
                    "type": "string",
                    "enum": [
                      "yes"
                    ],
                    "description": "Privacy consent. Must be \"yes\"; the user must have consented explicitly."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Accepted",
            "headers": {
              "api-version": {
                "$ref": "#/components/headers/ApiVersion"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "ok"
                  ],
                  "properties": {
                    "ok": {
                      "type": "boolean",
                      "const": true
                    },
                    "id": {
                      "type": "string",
                      "format": "uuid",
                      "description": "Consultation record id"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "description": "Validation error (codes: invalid, required, phone, email, privacy)",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "429": {
            "description": "Duplicate submission within 10 minutes (code: duplicate) or rate limited (code: rate_limited)",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "500": {
            "description": "Internal error (code: insert)",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          },
          "503": {
            "description": "Intake temporarily unavailable (code: unavailable)",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    },
    "/.well-known/mcp": {
      "post": {
        "operationId": "callMcp",
        "summary": "Call Veteran Law Firm public MCP server",
        "description": "Streamable HTTP MCP endpoint (JSON-RPC 2.0). Tools are read-only and expose published public directory data only. 60 requests/min per IP.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "jsonrpc",
                  "method"
                ],
                "properties": {
                  "jsonrpc": {
                    "type": "string",
                    "const": "2.0"
                  },
                  "id": {
                    "type": [
                      "string",
                      "number",
                      "null"
                    ]
                  },
                  "method": {
                    "type": "string",
                    "description": "MCP method, e.g. initialize, tools/list, tools/call"
                  },
                  "params": {
                    "type": "object",
                    "additionalProperties": true
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "MCP JSON-RPC response",
            "headers": {
              "api-version": {
                "$ref": "#/components/headers/ApiVersion"
              },
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              },
              "text/event-stream": {
                "schema": {
                  "type": "string",
                  "description": "SSE stream of JSON-RPC messages"
                }
              }
            }
          },
          "400": {
            "description": "Invalid MCP request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              }
            }
          },
          "403": {
            "description": "Invalid Origin header",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JsonRpcResponse"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited",
            "headers": {
              "RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              },
              "RateLimit-Reset": {
                "$ref": "#/components/headers/RateLimitReset"
              },
              "Retry-After": {
                "$ref": "#/components/headers/RetryAfter"
              }
            },
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/Problem"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ConsultationRequest": {
        "type": "object",
        "required": [
          "name",
          "phone",
          "privacy"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 80,
            "description": "Requester name"
          },
          "phone": {
            "type": "string",
            "maxLength": 30,
            "description": "Korean phone number, e.g. 010-0000-0000"
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "Optional email"
          },
          "message": {
            "type": "string",
            "maxLength": 2000,
            "description": "Optional case summary"
          },
          "practice": {
            "type": "string",
            "description": "Optional practice-area slug from listPracticeAreas / llms.txt"
          },
          "office": {
            "type": "string",
            "description": "Optional office slug"
          },
          "lawyer": {
            "type": "string",
            "description": "Optional lawyer slug"
          },
          "privacy": {
            "type": "string",
            "enum": [
              "yes"
            ],
            "description": "Privacy consent. Must be \"yes\"; the user must have consented explicitly."
          }
        }
      },
      "Problem": {
        "type": "object",
        "description": "RFC 9457 problem details. `code` is the stable machine-readable identifier; `detail` is a human-readable Korean message with a resolution hint.",
        "required": [
          "title",
          "status",
          "code",
          "detail"
        ],
        "properties": {
          "type": {
            "type": "string",
            "format": "uri",
            "description": "URI reference identifying the problem type"
          },
          "title": {
            "type": "string",
            "description": "Short human-readable summary (English)"
          },
          "status": {
            "type": "integer",
            "description": "HTTP status code"
          },
          "detail": {
            "type": "string",
            "description": "Human-readable explanation with a resolution hint (Korean)"
          },
          "code": {
            "type": "string",
            "description": "Stable machine-readable error code",
            "enum": [
              "invalid",
              "required",
              "phone",
              "email",
              "privacy",
              "unavailable",
              "duplicate",
              "rate_limited",
              "insert",
              "failed",
              "not_found",
              "method_not_allowed"
            ]
          },
          "instance": {
            "type": "string",
            "description": "Request path that produced the error"
          }
        }
      },
      "JsonRpcResponse": {
        "type": "object",
        "description": "JSON-RPC 2.0 response envelope used by the MCP endpoint.",
        "required": [
          "jsonrpc"
        ],
        "properties": {
          "jsonrpc": {
            "type": "string",
            "const": "2.0"
          },
          "id": {
            "type": [
              "string",
              "number",
              "null"
            ]
          },
          "result": {
            "type": "object",
            "additionalProperties": true
          },
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "integer"
              },
              "message": {
                "type": "string"
              },
              "data": {}
            }
          }
        }
      }
    },
    "headers": {
      "ApiVersion": {
        "description": "Date-based API version (current: 2026-08-31). Changes only on breaking changes.",
        "schema": {
          "type": "string"
        }
      },
      "RateLimitLimit": {
        "description": "Requests allowed in the current window",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitRemaining": {
        "description": "Requests remaining in the current window",
        "schema": {
          "type": "integer"
        }
      },
      "RateLimitReset": {
        "description": "Seconds until the window resets",
        "schema": {
          "type": "integer"
        }
      },
      "RetryAfter": {
        "description": "Seconds to wait before retrying (RFC 9110)",
        "schema": {
          "type": "integer"
        }
      }
    }
  }
}
