{
  "openapi": "3.1.0",
  "info": {
    "title": "Jedsada Public Calculation API",
    "version": "1.0.0",
    "summary": "เครื่องคำนวณโหราศาสตร์ไทยแบบข้อมูลล้วน — ไม่มีคำทำนายประกอบ",
    "description": "ผลลัพธ์ทุกตัวมาจาก engine ที่ vector-locked ในโปรเจกต์ ไม่ใช่ข้อความที่สร้างจากโมเดลภาษา\n\n**สถานะบริการ:** ทุก endpoint ตอบ `503 api_disabled` จนกว่าเจ้าของจะเปิดใช้งาน — ดู `GET /health`\n\n**ป้ายระดับความเชื่อมั่น:** ผลของสุริยยาตร์แนบ `tier` + `tierNote` รายดวง (ลัคนา/ตนุเศษ = `partial`) ผู้เรียกที่นำผลไปแสดงต่อผู้ใช้ปลายทาง **ต้องแสดงป้ายนี้ด้วย** ตาม Owner Decision 2026-07-23",
    "contact": {
      "name": "เจษฎา พยากรณ์",
      "url": "https://jedsada-payakorn.com/api-docs"
    }
  },
  "servers": [
    {
      "url": "https://jedsada-payakorn.com/api/public/v1",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "meta",
      "description": "สถานะบริการ"
    },
    {
      "name": "calculations",
      "description": "เครื่องคำนวณ (ต้องมี API key)"
    },
    {
      "name": "admin",
      "description": "จัดการ API key — ต้องผ่าน Cloudflare Access"
    }
  ],
  "security": [
    {
      "bearerAuth": []
    },
    {
      "apiKeyHeader": []
    }
  ],
  "paths": {
    "/admin/keys": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "รายการ API key + ยอดใช้งานเดือนปัจจุบัน",
        "security": [
          {
            "cloudflareAccess": []
          }
        ],
        "responses": {
          "200": {
            "description": "รายการ key",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "month": {
                      "type": "string",
                      "description": "เดือนเวลาไทย รูปแบบ YYYY-MM"
                    },
                    "keys": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/KeySummary"
                      }
                    }
                  }
                }
              }
            }
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      },
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "ออก API key ใบใหม่",
        "description": "⚠️ ค่า `apiKey` (ขึ้นต้น `jsd_`) แสดง **ครั้งเดียว** ระบบเก็บเฉพาะ HMAC hash กู้คืนไม่ได้",
        "security": [
          {
            "cloudflareAccess": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "name"
                ],
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "tier": {
                    "type": "string",
                    "default": "standard"
                  },
                  "rateLimitPerMinute": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "เพดานรายตัว · null = ใช้ค่ากลางจาก env"
                  },
                  "monthlyQuota": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "description": "โควตาต่อเดือน · null = ไม่จำกัด"
                  },
                  "expiresAt": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time",
                    "description": "null = ไม่หมดอายุ"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "ออก key แล้ว",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "apiKey": {
                      "type": "string",
                      "description": "raw key — แสดงครั้งเดียว"
                    },
                    "key": {
                      "$ref": "#/components/schemas/KeySummary"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/admin/keys/{keyId}/revoke": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "เพิกถอน key",
        "security": [
          {
            "cloudflareAccess": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KeyId"
          }
        ],
        "responses": {
          "200": {
            "$ref": "#/components/responses/KeyEnvelope"
          },
          "404": {
            "$ref": "#/components/responses/KeyNotFound"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/admin/keys/{keyId}/update": {
      "post": {
        "tags": [
          "admin"
        ],
        "summary": "แก้แพ็กเกจ / เพดาน / โควตา / ต่ออายุ โดยไม่เปลี่ยนตัว key",
        "description": "ส่ง `null` เพื่อล้างค่าของฟิลด์นั้น · ฟิลด์ที่ไม่ส่งมาคงเดิม",
        "security": [
          {
            "cloudflareAccess": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KeyId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "tier": {
                    "type": "string"
                  },
                  "rateLimitPerMinute": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "monthlyQuota": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "expiresAt": {
                    "type": [
                      "string",
                      "null"
                    ],
                    "format": "date-time"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/KeyEnvelope"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "404": {
            "$ref": "#/components/responses/KeyNotFound"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/admin/keys/{keyId}/usage": {
      "get": {
        "tags": [
          "admin"
        ],
        "summary": "ยอดใช้งานย้อนหลัง 12 เดือนของ key",
        "security": [
          {
            "cloudflareAccess": []
          }
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/KeyId"
          }
        ],
        "responses": {
          "200": {
            "description": "ยอดรายเดือน",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "$ref": "#/components/schemas/KeySummary"
                    },
                    "usage": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "month": {
                            "type": "string"
                          },
                          "requests": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/KeyNotFound"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/calculations/auspicious-days": {
      "post": {
        "tags": [
          "calculations"
        ],
        "summary": "ฤกษ์รายเดือนตามประเภทงาน",
        "description": "อ่าน dataset ground truth ที่ export จาก Dart engine — API ไม่คำนวณฤกษ์เอง\n\n**หน้าต่างที่เปิด:** เดือนปัจจุบัน ±3 เดือน (เวลาไทย) เท่านั้น เกินจากนี้ตอบ `403 outside_free_window` · dataset ครอบคลุม พ.ศ. 2567–2569\n\n`topDates` (3 วันเด่น) ติดป้าย `draft_ranker` เสมอ — น้ำหนักจัดอันดับยังเป็นฉบับร่าง ส่วนเกณฑ์รายวันคือข้อมูลที่ล็อกแล้ว\n\nฤกษ์ผูกดวงเจ้าชะตาอยู่ที่ `/calculations/auspicious-intervals` — คำนวณสด ผูกดวง 1–2 คน และคืนผลเป็นช่วงเวลา",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "workType",
                  "yearBe",
                  "month"
                ],
                "additionalProperties": false,
                "properties": {
                  "workType": {
                    "type": "string",
                    "description": "รหัสประเภทงาน 8 หมวด — ค่าที่รับได้อยู่ในข้อความ error เมื่อส่งผิด"
                  },
                  "yearBe": {
                    "type": "integer",
                    "description": "ปี พ.ศ."
                  },
                  "month": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 12
                  }
                }
              },
              "example": {
                "workType": "wedding",
                "yearBe": 2569,
                "month": 8
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Calculation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "description": "อยู่นอกหน้าต่างที่เปิด (`outside_free_window`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/calculations/auspicious-intervals": {
      "post": {
        "tags": [
          "calculations"
        ],
        "summary": "ฤกษ์ผูกดวง — ช่วงเวลาสำหรับเจ้าชะตา 1–2 คน",
        "description": "คำนวณสดจาก engine สุริยยาตร์ตัวเดียวกับ `/calculations/suriyayat` (ไม่อ่าน dataset จึงรับช่วงวันที่อิสระได้ ไม่ติดเพดาน พ.ศ. 2567–2569)\n\n**ต่างจาก `/calculations/auspicious-days`:** ตัวนั้นให้ \"วันดี\" รายเดือนแบบฤกษ์กลาง ไม่ใช้ข้อมูลวันเกิด · ตัวนี้ผูกดวงเจ้าชะตาและคืนผลเป็น**ช่วงเวลาระดับนาที** (เช่น 09:18–09:42) เพราะลัคนาเสวยฤกษ์ละราว 35–90 นาที\n\n**เกณฑ์ที่ใช้:** กาลโยครายปี · ดิถีโชค/พิฆาต/เรียงหมอน · ฤกษ์บนหมวดที่หนุนงานสมรส · วันกาลกิณีตามทักษาของแต่ละฝ่าย · สมพงศ์วันเกิดของคู่ · ลัคนาดวงฤกษ์\n\nวันกาลกิณีเป็น**ข้อควรทราบ ไม่ตัดช่วงเวลาทิ้ง** — ระบบแสดงว่าอะไรผ่านและอะไรควรระวัง ไม่ประกาศว่าดีที่สุด และไม่มีคำทำนาย\n\nทุกช่วงติดสถานะ `verification`: การคำนวณ `VERIFIED` (ตรวจกับปฏิทินโหราศาสตร์แล้ว) ส่วนการตีความ `PROVISIONAL` (บางกฎมาจากแบบแผนที่นิยมใช้ ยังรอเทียบต้นฉบับตำราให้ครบ)",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "additionalProperties": false,
                "required": [
                  "range",
                  "event",
                  "people"
                ],
                "properties": {
                  "range": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "start",
                      "end"
                    ],
                    "description": "ISO 8601 พร้อม offset · ต้องไม่ก่อนต้นเดือนปัจจุบัน ไม่เกิน +6 เดือน และยาวไม่เกิน 30 วัน",
                    "properties": {
                      "start": {
                        "type": "string",
                        "examples": [
                          "2026-09-01T00:00:00+07:00"
                        ]
                      },
                      "end": {
                        "type": "string",
                        "examples": [
                          "2026-09-30T23:59:59+07:00"
                        ]
                      }
                    }
                  },
                  "event": {
                    "type": "object",
                    "additionalProperties": false,
                    "required": [
                      "workTypeCode",
                      "startActionCode",
                      "zoneId",
                      "latitudeE6",
                      "longitudeE6",
                      "allowedWindows"
                    ],
                    "properties": {
                      "workTypeCode": {
                        "type": "string",
                        "enum": [
                          "WEDDING_CEREMONY",
                          "WEDDING_REGISTRATION",
                          "WEDDING_RITUAL"
                        ]
                      },
                      "startActionCode": {
                        "type": "string",
                        "description": "นิยามว่าเวลาเริ่มนับจากการกระทำใด เช่น SIGNATURE_COMPLETED = นับตอนลงนามเสร็จ ไม่ใช่ตอนเข้าห้อง"
                      },
                      "zoneId": {
                        "type": "string",
                        "examples": [
                          "Asia/Bangkok"
                        ]
                      },
                      "latitudeE6": {
                        "type": "integer"
                      },
                      "longitudeE6": {
                        "type": "integer"
                      },
                      "allowedWindows": {
                        "type": "array",
                        "minItems": 1,
                        "description": "ช่วงที่เจ้าภาพสะดวกจัดงานจริง ใช้จำกัดขอบเขตการค้นหา",
                        "items": {
                          "type": "object",
                          "additionalProperties": false,
                          "required": [
                            "weekdayCodes",
                            "startTime",
                            "endTime"
                          ],
                          "properties": {
                            "weekdayCodes": {
                              "type": "array",
                              "minItems": 1,
                              "items": {
                                "type": "string",
                                "enum": [
                                  "MON",
                                  "TUE",
                                  "WED",
                                  "THU",
                                  "FRI",
                                  "SAT",
                                  "SUN"
                                ]
                              }
                            },
                            "startTime": {
                              "type": "string",
                              "pattern": "^\\d{2}:\\d{2}:\\d{2}$"
                            },
                            "endTime": {
                              "type": "string",
                              "pattern": "^\\d{2}:\\d{2}:\\d{2}$"
                            }
                          }
                        }
                      }
                    }
                  },
                  "people": {
                    "type": "array",
                    "minItems": 1,
                    "maxItems": 2,
                    "description": "เจ้าชะตา 1–2 คน · คนที่สองเป็นตัวเลือก",
                    "items": {
                      "type": "object",
                      "additionalProperties": false,
                      "required": [
                        "roleCode",
                        "displayName",
                        "birth"
                      ],
                      "properties": {
                        "roleCode": {
                          "type": "string",
                          "enum": [
                            "PARTNER_A",
                            "PARTNER_B"
                          ]
                        },
                        "displayName": {
                          "type": "string",
                          "description": "ใช้แสดงผลเท่านั้น ไม่มีผลต่อการคำนวณ"
                        },
                        "birth": {
                          "type": "object",
                          "additionalProperties": false,
                          "required": [
                            "localDate",
                            "localTime",
                            "zoneId",
                            "latitudeE6",
                            "longitudeE6",
                            "timeAccuracy"
                          ],
                          "properties": {
                            "localDate": {
                              "type": "string",
                              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
                              "description": "ปี ค.ศ."
                            },
                            "localTime": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "pattern": "^\\d{2}:\\d{2}:\\d{2}$"
                            },
                            "zoneId": {
                              "type": "string",
                              "examples": [
                                "Asia/Bangkok"
                              ]
                            },
                            "latitudeE6": {
                              "type": "integer",
                              "description": "ละติจูดสถานที่เกิด × 1e6 — วิธีลัคนาของตำรานี้ไม่มีเทอมพิกัด ค่านี้จึงเก็บเป็น metadata"
                            },
                            "longitudeE6": {
                              "type": "integer"
                            },
                            "timeAccuracy": {
                              "description": "ความแม่นของเวลาเกิด — `localTime` เป็น null ได้เฉพาะเมื่อ type = UNKNOWN เท่านั้น ระบบจะไม่เดาเวลาเกิดแทนผู้ใช้",
                              "oneOf": [
                                {
                                  "type": "object",
                                  "additionalProperties": false,
                                  "required": [
                                    "type",
                                    "uncertaintyMinutes"
                                  ],
                                  "properties": {
                                    "type": {
                                      "const": "EXACT"
                                    },
                                    "uncertaintyMinutes": {
                                      "const": 0
                                    }
                                  }
                                },
                                {
                                  "type": "object",
                                  "additionalProperties": false,
                                  "required": [
                                    "type",
                                    "uncertaintyMinutes"
                                  ],
                                  "properties": {
                                    "type": {
                                      "const": "APPROXIMATE"
                                    },
                                    "uncertaintyMinutes": {
                                      "type": "integer",
                                      "minimum": 1
                                    }
                                  }
                                },
                                {
                                  "type": "object",
                                  "additionalProperties": false,
                                  "required": [
                                    "type",
                                    "fromLocalTime",
                                    "toLocalTime"
                                  ],
                                  "properties": {
                                    "type": {
                                      "const": "RANGE"
                                    },
                                    "fromLocalTime": {
                                      "type": "string",
                                      "pattern": "^\\d{2}:\\d{2}:\\d{2}$"
                                    },
                                    "toLocalTime": {
                                      "type": "string",
                                      "pattern": "^\\d{2}:\\d{2}:\\d{2}$"
                                    }
                                  }
                                },
                                {
                                  "type": "object",
                                  "additionalProperties": false,
                                  "required": [
                                    "type"
                                  ],
                                  "properties": {
                                    "type": {
                                      "const": "UNKNOWN"
                                    }
                                  }
                                }
                              ]
                            }
                          }
                        }
                      }
                    }
                  },
                  "policies": {
                    "type": "object",
                    "description": "ประกาศนโยบายการคำนวณที่ต้องการ · v1 รองรับชุดเดียว — ถ้าส่งค่าที่ไม่รองรับมาจะตอบ 422 `unsupported_policy` ไม่ใช่เงียบ ๆ ใช้ค่าอื่นแทน"
                  }
                }
              },
              "example": {
                "range": {
                  "start": "2026-09-01T00:00:00+07:00",
                  "end": "2026-09-26T23:59:59+07:00"
                },
                "event": {
                  "workTypeCode": "WEDDING_CEREMONY",
                  "startActionCode": "CEREMONY_FORMALLY_BEGINS",
                  "zoneId": "Asia/Bangkok",
                  "latitudeE6": 13756331,
                  "longitudeE6": 100501765,
                  "allowedWindows": [
                    {
                      "weekdayCodes": [
                        "SAT",
                        "SUN"
                      ],
                      "startTime": "07:00:00",
                      "endTime": "12:00:00"
                    }
                  ]
                },
                "people": [
                  {
                    "roleCode": "PARTNER_A",
                    "displayName": "สมมติ ก",
                    "birth": {
                      "localDate": "1992-03-14",
                      "localTime": "08:42:00",
                      "zoneId": "Asia/Bangkok",
                      "latitudeE6": 13756331,
                      "longitudeE6": 100501765,
                      "timeAccuracy": {
                        "type": "EXACT",
                        "uncertaintyMinutes": 0
                      }
                    }
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Calculation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "422": {
            "description": "ข้อมูลใช้ไม่ได้ (`invalid_input`) หรือขอนโยบายที่ไม่รองรับ (`unsupported_policy`)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/calculations/chakratipani": {
      "post": {
        "tags": [
          "calculations"
        ],
        "summary": "จักรทีปนี (ขั้นต้น)",
        "description": "วรรคคัมภีร์จักรทีปนีตามลัคนาและราศีของดาวแต่ละดวง · ตำแหน่งที่ต้นฉบับไม่มีวรรคจะได้ `verse: null` ไม่เดาแทน · วรรคที่ต้นฉบับมีเนื้อหาอ่อนไหวติดธง `sensitive` — ผู้เรียกควรกรองตามบริบทที่นำไปใช้ · ช่วงปีเกิด ค.ศ. 1857–2057",
        "requestBody": {
          "$ref": "#/components/requestBodies/BirthMoment"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Calculation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/calculations/graha-ayu": {
      "post": {
        "tags": [
          "calculations"
        ],
        "summary": "ดาวเสวยอายุ–แทรก + อายุย่าง",
        "description": "ช่วงปีเกิดที่รองรับ: ค.ศ. 1900–2100",
        "requestBody": {
          "$ref": "#/components/requestBodies/BirthMomentAsOf"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Calculation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/calculations/graha-ayu-table": {
      "post": {
        "tags": [
          "calculations"
        ],
        "summary": "ตารางพระเคราะห์เสวยอายุ (ทั้งวง)",
        "description": "ช่วงเสวยอายุ+แทรก ณ `asOfDate` พร้อม **ตารางมหาทักษาตลอดวง** (ต่างจาก `/calculations/graha-ayu` ที่คืนเฉพาะช่วงปัจจุบัน) · `asOfDate` ต้องไม่ก่อนวันเกิด · ช่วงปีเกิด ค.ศ. 1900–2100",
        "requestBody": {
          "$ref": "#/components/requestBodies/BirthMomentAsOf"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Calculation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/calculations/kali": {
      "post": {
        "tags": [
          "calculations"
        ],
        "summary": "กาลชะตา — เดินกาลีรายปี",
        "description": "เลขอายุ / ดาวตก / ดาวกาลี + แนวทางรับมือ (ข้อความชุดเดียวกับหน้าเว็บ) ณ `asOfDate` · ช่วงปีเกิดที่รองรับ ค.ศ. 1900–2100",
        "requestBody": {
          "$ref": "#/components/requestBodies/BirthMomentAsOf"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Calculation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/calculations/life-guidance": {
      "post": {
        "tags": [
          "calculations"
        ],
        "summary": "คำทำนายพื้นดวง 6 ด้าน (คลัง 34 ข้อ)",
        "description": "ข้อความ verbatim จากคลังต้นฉบับเท่านั้น ไม่มีการเรียบเรียงใหม่ · แนบ `predictionId` และ `notice` เรื่องโทนการใช้งานทุก response · ช่วงปีเกิดที่รองรับ ค.ศ. 1932–2027",
        "requestBody": {
          "$ref": "#/components/requestBodies/BirthMoment"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Calculation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/calculations/luck-check": {
      "post": {
        "tags": [
          "calculations"
        ],
        "summary": "ตรวจโชคหลายวิธี",
        "description": "รันทุกวิธีที่ข้อมูลพอ ณ `asOfDate` · แต่ละวิธีติดสถานะมาด้วย — `availability` (calculated / missingInput / error) และ `verification` ระดับการตรวจสอบของกฎ · **วิธีที่ยังเป็น research-only ไม่ควรใช้ตัดสินใจ ผู้เรียกต้องกรองเอง** · ส่ง `partnerBirthDate`/`partnerBirthTime` คู่กันเพื่อเปิดวิธีที่ต้องใช้ข้อมูลคู่",
        "requestBody": {
          "$ref": "#/components/requestBodies/LuckCheck"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Calculation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/calculations/lucky-color": {
      "post": {
        "tags": [
          "calculations"
        ],
        "summary": "สีมงคลประจำปีจากวงทักษาจร",
        "description": "ศรี = สีหลัก, เดช = สีเสริม พร้อมค่า hex · ช่วงปีเกิดที่รองรับ ค.ศ. 1900–2100\n\n`asOfDate` ทำให้ผลลัพธ์ deterministic ต่อ input โดยไม่ผูกกับเวลาที่เรียก",
        "requestBody": {
          "$ref": "#/components/requestBodies/BirthMomentAsOf"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Calculation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/calculations/lucky-number": {
      "post": {
        "tags": [
          "calculations"
        ],
        "summary": "เลขมงคล 1–8 (ดาว/ภูมิ/tier)",
        "description": "ช่วงปีเกิดที่รองรับ: ค.ศ. 1932–2027 (พึ่งตาราง 4 ฐาน)",
        "requestBody": {
          "$ref": "#/components/requestBodies/BirthMoment"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Calculation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/calculations/mahathaksa": {
      "post": {
        "tags": [
          "calculations"
        ],
        "summary": "มหาทักษา — ดาวเกิด (กฎรุ่งอรุณ) + วงภูมิ 8 ตำแหน่ง",
        "description": "ช่วงปีเกิดที่รองรับ: ค.ศ. 1900–2100",
        "requestBody": {
          "$ref": "#/components/requestBodies/BirthMoment"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Calculation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/calculations/name-numerology": {
      "post": {
        "tags": [
          "calculations"
        ],
        "summary": "เลขศาสตร์ชื่อ-นามสกุล",
        "description": "แปลงอักขระเป็นค่าตามตำราแล้วรวมผล · อักขระที่ตำราไม่ได้ให้ค่าไว้จะอยู่ใน `unsupportedTokens` และ `isComplete: false` (ไม่แทนค่าเดา) · ผลรวมที่คลังไม่มีคำอธิบายจะได้ `meaning: null`",
        "requestBody": {
          "$ref": "#/components/requestBodies/NameParts"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Calculation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/calculations/seven-number-four-base": {
      "post": {
        "tags": [
          "calculations"
        ],
        "summary": "เลข 7 ตัว 4 ฐาน",
        "description": "ช่วงปีเกิดที่รองรับ: ค.ศ. 1932–2027 (จำกัดด้วยตารางจันทรคติ)",
        "requestBody": {
          "$ref": "#/components/requestBodies/BirthMoment"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Calculation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/calculations/seven-number-nine-base": {
      "post": {
        "tags": [
          "calculations"
        ],
        "summary": "เลข 7 ตัว 9 ฐาน",
        "description": "ฐาน 1–4 จาก engine เดียวกับ `seven-number-four-base` แล้วต่อฐาน 5–9 · ธง `isRahuBorn` บอกเส้นทางของคนเกิดพุธกลางคืน · ช่วงปีเกิด ค.ศ. 1932–2027 (ตารางจันทรคติ)",
        "requestBody": {
          "$ref": "#/components/requestBodies/BirthMoment"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Calculation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/calculations/sompong": {
      "post": {
        "tags": [
          "calculations"
        ],
        "summary": "สมพงศ์ภาค 1 — วัน/เดือน/ปีนักษัตร/นามอัฏฐจักร",
        "description": "ช่วงปีเกิดที่รองรับ ค.ศ. 1932–2027 ทั้งสองฝ่าย · ชั้นนาค dormant ตามมติเจ้าของ 2026-07-14 — จุดที่ตำราไม่ครบตอบ `pending` ไม่เดา",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "male",
                  "female"
                ],
                "additionalProperties": false,
                "properties": {
                  "male": {
                    "$ref": "#/components/schemas/BirthMomentObject"
                  },
                  "female": {
                    "$ref": "#/components/schemas/BirthMomentObject"
                  }
                }
              },
              "example": {
                "male": {
                  "birthDate": "1990-05-14",
                  "birthTime": "07:30"
                },
                "female": {
                  "birthDate": "1992-11-02",
                  "birthTime": "16:45"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Calculation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/calculations/suriyayat": {
      "post": {
        "tags": [
          "calculations"
        ],
        "summary": "สุริยยาตร์ — พื้นดวง 10 ดาว + ลัคนา + ตนุเศษ",
        "description": "ช่วงปีเกิดที่รองรับ: ค.ศ. 1857–2057 (พ.ศ. 2400–2600)\n\nผลลัพธ์แนบ `tier` รายดวง — ลัคนาและตนุเศษเป็น `partial`",
        "requestBody": {
          "$ref": "#/components/requestBodies/BirthMoment"
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Calculation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/calculations/suriyayat-ingress": {
      "post": {
        "tags": [
          "calculations"
        ],
        "summary": "ดาวย้าย — ราศีถัดไปของแต่ละดวง ละเอียดระดับนาที",
        "description": "ช่วงปีที่รองรับ ค.ศ. 1857–2057\n\n⚠️ ต้นทุน CPU: สแกน 400 วัน ≈ 60ms — ผู้เรียกที่ต้องการผลเร็วควรลด `horizonDays`\n\nกลไกสแกนเป็น wiring ฝั่งเว็บ ไม่ได้อยู่ใน verify tool ต้นทาง — trace ระบุ `provisional` ตามจริง",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "fromDate"
                ],
                "additionalProperties": false,
                "properties": {
                  "fromDate": {
                    "$ref": "#/components/schemas/Date"
                  },
                  "fromTime": {
                    "$ref": "#/components/schemas/Time",
                    "default": "00:00"
                  },
                  "horizonDays": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 400,
                    "default": 400
                  }
                }
              },
              "example": {
                "fromDate": "2026-07-27",
                "horizonDays": 90
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Calculation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/calculations/suriyayat-transit": {
      "post": {
        "tags": [
          "calculations"
        ],
        "summary": "ดาวจรเทียบพื้นดวง",
        "description": "เรือนเทียบลัคน์กำเนิด + คู่กระทบ (กุม/เล็ง) · ช่วงปีที่รองรับ ค.ศ. 1857–2057 ทั้งสอง moment\n\nขั้นผูกเรือนใน trace เป็น `provisional` เพราะพึ่ง `natalLagna` ที่เป็น `partial`",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "birthDate",
                  "birthTime",
                  "transitDate",
                  "transitTime"
                ],
                "additionalProperties": false,
                "properties": {
                  "birthDate": {
                    "$ref": "#/components/schemas/Date"
                  },
                  "birthTime": {
                    "$ref": "#/components/schemas/Time"
                  },
                  "transitDate": {
                    "$ref": "#/components/schemas/Date"
                  },
                  "transitTime": {
                    "$ref": "#/components/schemas/Time"
                  }
                }
              },
              "example": {
                "birthDate": "1990-05-14",
                "birthTime": "07:30",
                "transitDate": "2026-07-27",
                "transitTime": "09:00"
              }
            }
          }
        },
        "responses": {
          "200": {
            "$ref": "#/components/responses/Calculation"
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    },
    "/health": {
      "get": {
        "tags": [
          "meta"
        ],
        "summary": "สถานะบริการ + รายชื่อเครื่องคำนวณ",
        "security": [],
        "responses": {
          "200": {
            "description": "บริการพร้อมใช้งาน",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "status",
                    "apiVersion",
                    "calculators"
                  ],
                  "properties": {
                    "status": {
                      "type": "string",
                      "const": "ok"
                    },
                    "apiVersion": {
                      "type": "string",
                      "const": "v1"
                    },
                    "calculators": {
                      "type": "array",
                      "items": {
                        "type": "string"
                      }
                    }
                  }
                }
              }
            }
          },
          "503": {
            "$ref": "#/components/responses/Disabled"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "description": "`Authorization: Bearer jsd_...`"
      },
      "apiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "ทางเลือกแทน Authorization header"
      },
      "cloudflareAccess": {
        "type": "apiKey",
        "in": "header",
        "name": "cf-access-jwt-assertion",
        "description": "เส้นทาง admin ต้องผ่าน Cloudflare Access — JWT ถูกตรวจกับ team domain + AUD ของเจ้าของ ไม่ใช่การเชื่อ header ระบุตัวตนดิบ"
      }
    },
    "parameters": {
      "KeyId": {
        "name": "keyId",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string"
        }
      }
    },
    "schemas": {
      "Date": {
        "type": "string",
        "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
        "description": "ปฏิทินสุริยคติ ค.ศ. รูปแบบ YYYY-MM-DD",
        "examples": [
          "1990-05-14"
        ]
      },
      "Time": {
        "type": "string",
        "pattern": "^\\d{2}:\\d{2}$",
        "description": "เวลาไทย 24 ชั่วโมง รูปแบบ HH:MM",
        "examples": [
          "07:30"
        ]
      },
      "BirthMomentObject": {
        "type": "object",
        "required": [
          "birthDate",
          "birthTime"
        ],
        "additionalProperties": false,
        "properties": {
          "birthDate": {
            "$ref": "#/components/schemas/Date"
          },
          "birthTime": {
            "$ref": "#/components/schemas/Time"
          }
        }
      },
      "CalculationEnvelope": {
        "type": "object",
        "required": [
          "calculator",
          "apiVersion",
          "input",
          "result",
          "trace"
        ],
        "properties": {
          "calculator": {
            "type": "string"
          },
          "apiVersion": {
            "type": "string",
            "const": "v1"
          },
          "input": {
            "type": "object",
            "description": "input ที่ผ่านการตรวจแล้ว สะท้อนกลับมาเพื่อยืนยัน"
          },
          "result": {
            "description": "ผลลัพธ์ — โครงสร้างต่างกันตามเครื่องคำนวณ"
          },
          "trace": {
            "description": "ขั้นตอนการคำนวณพร้อมป้ายระดับความเชื่อมั่นรายขั้น"
          }
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "enum": [
                  "invalid_input",
                  "invalid_json",
                  "unknown_field",
                  "invalid_api_key",
                  "api_key_revoked",
                  "api_key_expired",
                  "forbidden",
                  "outside_free_window",
                  "not_found",
                  "key_not_found",
                  "method_not_allowed",
                  "rate_limited",
                  "monthly_quota_exceeded",
                  "api_disabled",
                  "internal_error"
                ]
              },
              "message": {
                "type": "string"
              }
            }
          }
        }
      },
      "KeySummary": {
        "type": "object",
        "description": "ไม่มีค่า key ดิบ — ระบุตัวด้วย keyPrefix",
        "properties": {
          "id": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "keyPrefix": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "active",
              "revoked"
            ]
          },
          "tier": {
            "type": "string"
          },
          "rateLimitPerMinute": {
            "type": [
              "integer",
              "null"
            ]
          },
          "monthlyQuota": {
            "type": [
              "integer",
              "null"
            ]
          },
          "expiresAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "currentMonthRequests": {
            "type": "integer"
          }
        }
      },
      "NamePartsObject": {
        "type": "object",
        "required": [
          "name",
          "surname"
        ],
        "additionalProperties": false,
        "properties": {
          "name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "ชื่อตัว"
          },
          "surname": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "นามสกุล"
          }
        }
      },
      "LuckCheckObject": {
        "type": "object",
        "required": [
          "birthDate",
          "birthTime",
          "asOfDate"
        ],
        "additionalProperties": false,
        "properties": {
          "birthDate": {
            "$ref": "#/components/schemas/Date"
          },
          "birthTime": {
            "$ref": "#/components/schemas/Time"
          },
          "asOfDate": {
            "$ref": "#/components/schemas/Date"
          },
          "asOfTime": {
            "$ref": "#/components/schemas/Time"
          },
          "partnerBirthDate": {
            "$ref": "#/components/schemas/Date"
          },
          "partnerBirthTime": {
            "$ref": "#/components/schemas/Time"
          }
        },
        "description": "partnerBirthDate กับ partnerBirthTime ต้องส่งมาคู่กันเท่านั้น · asOfTime ไม่ส่ง = 12:00"
      }
    },
    "requestBodies": {
      "BirthMoment": {
        "required": true,
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/BirthMomentObject"
            },
            "example": {
              "birthDate": "1990-05-14",
              "birthTime": "07:30"
            }
          }
        }
      },
      "BirthMomentAsOf": {
        "required": true,
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "required": [
                "birthDate",
                "birthTime",
                "asOfDate"
              ],
              "additionalProperties": false,
              "properties": {
                "birthDate": {
                  "$ref": "#/components/schemas/Date"
                },
                "birthTime": {
                  "$ref": "#/components/schemas/Time"
                },
                "asOfDate": {
                  "$ref": "#/components/schemas/Date"
                }
              }
            },
            "example": {
              "birthDate": "1990-05-14",
              "birthTime": "07:30",
              "asOfDate": "2026-07-27"
            }
          }
        }
      },
      "NameParts": {
        "required": true,
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/NamePartsObject"
            },
            "example": {
              "name": "เจษฎา",
              "surname": "พยากรณ์"
            }
          }
        }
      },
      "LuckCheck": {
        "required": true,
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/LuckCheckObject"
            },
            "example": {
              "birthDate": "1990-05-14",
              "birthTime": "07:30",
              "asOfDate": "2026-07-28"
            }
          }
        }
      }
    },
    "responses": {
      "Calculation": {
        "description": "ผลการคำนวณ",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/CalculationEnvelope"
            }
          }
        }
      },
      "BadRequest": {
        "description": "input ไม่ผ่านการตรวจ (`invalid_input` · `invalid_json` · `unknown_field`)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "key ไม่ถูกต้อง / ถูกเพิกถอน / หมดอายุ",
        "headers": {
          "www-authenticate": {
            "schema": {
              "type": "string"
            }
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "ไม่ผ่านด่านแอดมิน",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "KeyNotFound": {
        "description": "ไม่มี key นี้ (`key_not_found`)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "KeyEnvelope": {
        "description": "สถานะ key หลังดำเนินการ",
        "content": {
          "application/json": {
            "schema": {
              "type": "object",
              "properties": {
                "key": {
                  "$ref": "#/components/schemas/KeySummary"
                }
              }
            }
          }
        }
      },
      "RateLimited": {
        "description": "เกินเพดานต่อนาที (`rate_limited`) หรือเกินโควตาเดือน (`monthly_quota_exceeded`)",
        "headers": {
          "retry-after": {
            "schema": {
              "type": "integer"
            },
            "description": "วินาทีที่ควรรอ"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Disabled": {
        "description": "บริการยังไม่เปิด (`api_disabled`) — สถานะปกติจนกว่าเจ้าของจะเปิดใช้งาน",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  }
}
