{
  "openapi": "3.1.0",
  "info": {
    "title": "Jagannatha Hora Vedic Astrology API",
    "version": "1.0.0",
    "summary": "Vedic astrology (Jyotish) calculations as JSON: birth chart, divisional charts, dashas, yogas, doshas, ashtakavarga, panchang, transits, muhurta and marriage matching.",
    "description": "Human reference: https://horoscopes.today/api. Markdown: https://horoscopes.today/api.md. MCP server: https://horoscopes.today/mcp.\n\nEvery endpoint except POST /horoscope/pdf is open — no API key. Rate limits are enforced per client IP; the app-wide default is 200 requests/minute per IP and several endpoints set a tighter one, stated per operation.\n\nLast verified against the service source: 2026-08-31.",
    "license": {
      "name": "AGPL-3.0",
      "identifier": "AGPL-3.0-or-later"
    }
  },
  "servers": [
    {
      "url": "https://jagannatha-hora-359167915530.europe-west1.run.app",
      "description": "Production"
    }
  ],
  "externalDocs": {
    "description": "Human-readable reference",
    "url": "https://horoscopes.today/api"
  },
  "tags": [
    {
      "name": "Calculation",
      "description": "Chart, transit, panchang and muhurta computation."
    },
    {
      "name": "Lookup",
      "description": "Place resolution, service metadata and health."
    }
  ],
  "paths": {
    "/horoscope": {
      "post": {
        "operationId": "horoscope",
        "summary": "The complete Vedic birth chart for a birth moment.",
        "description": "The workhorse endpoint. One call returns every calculation the engine performs for a chart: 23 divisional charts, nakshatras and padas, yogas, doshas, chara karakas, sahams, upagrahas, special lagnas, sphutas, arudhas, house relationships, planetary states, five kinds of bala, ashtakavarga, graha and rasi dashas, Saturn transits and a longevity band. The response is large — on the order of 3.5 MB of JSON — and gzip-compressed on the wire. Computed fresh every time; the same inputs always produce the same output, so cache it on your side.\n\nRate limit: 30 requests/minute per IP.",
        "tags": [
          "Calculation"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "date": {
                    "type": "string",
                    "description": "Birth date, YYYY-MM-DD. Year must be 1900-2100, month 1-12, day 1-31."
                  },
                  "time": {
                    "type": "string",
                    "description": "Birth time, HH:MM:SS (24-hour). Hour 0-23, minute 0-59, second 0-59."
                  },
                  "place": {
                    "type": "string",
                    "description": "Birth place name. Cosmetic on the transit endpoints, where it defaults to an empty string; required on /horoscope."
                  },
                  "latitude": {
                    "type": "number",
                    "description": "Decimal degrees, -90 to 90."
                  },
                  "longitude": {
                    "type": "number",
                    "description": "Decimal degrees, -180 to 180."
                  },
                  "timezone": {
                    "type": "number",
                    "description": "UTC offset in hours, -12 to 14 (5.5 for IST). The service re-resolves the historical, DST-correct offset for the birth moment, so this is a hint rather than the last word."
                  },
                  "elevation": {
                    "type": "number",
                    "description": "Metres above sea level.",
                    "default": 0
                  },
                  "ayanamsa_mode": {
                    "type": "string",
                    "description": "One of 18 honoured modes. Case-insensitive; uppercased server-side. SENTHIL, SUNDAR_SS and SIDM_USER are accepted by validation but NOT applied by the engine — SENTHIL and SUNDAR_SS silently return a LAHIRI chart while reporting their own ayanamsa_value. Do not use them.",
                    "default": "LAHIRI",
                    "enum": [
                      "LAHIRI",
                      "TRUE_CITRA",
                      "TRUE_LAHIRI",
                      "KP",
                      "FAGAN",
                      "RAMAN",
                      "USHASHASHI",
                      "YUKTESHWAR",
                      "SURYASIDDHANTA",
                      "SURYASIDDHANTA_MSUN",
                      "ARYABHATA",
                      "ARYABHATA_MSUN",
                      "SS_CITRA",
                      "TRUE_REVATI",
                      "SS_REVATI",
                      "TRUE_PUSHYA",
                      "TRUE_MULA",
                      "KP-SENTHIL"
                    ]
                  },
                  "ayanamsa_value": {
                    "type": "number",
                    "description": "Custom ayanamsa in degrees, -30.0 to 30.0. Only meaningful with SIDM_USER."
                  },
                  "calculation_type": {
                    "type": "string",
                    "description": "\"drik\" (modern) or \"ss\" (Surya Siddhanta). Case-insensitive.",
                    "default": "drik"
                  },
                  "node_type": {
                    "type": "string",
                    "description": "Rahu/Ketu node: \"mean\" or \"true\".",
                    "default": "mean"
                  },
                  "bhava_madhya_method": {
                    "type": "integer",
                    "description": "House system, 1-5: 1=Equal-Lagna-Middle, 2=Equal-Lagna-Start, 3=Sripati, 4=KP, 5=Whole-Sign.",
                    "default": 1
                  },
                  "language": {
                    "type": "string",
                    "description": "One of en, hi, ka, ta, te.",
                    "default": "en"
                  },
                  "pravesha_type": {
                    "type": "integer",
                    "description": "0=Natal, 1=Annual, 2=Tithi-Pravesha.",
                    "default": 0
                  },
                  "years": {
                    "type": "integer",
                    "description": "Year number for annual/progressed charts, 1-120.",
                    "default": 1
                  },
                  "months": {
                    "type": "integer",
                    "description": "Month number for monthly charts, 1-12.",
                    "default": 1
                  },
                  "sixty_hours": {
                    "type": "integer",
                    "description": "60-hour period for hora progressions. The validator enforces 1-1000.",
                    "default": 1
                  },
                  "divisional_chart_factor": {
                    "type": "integer",
                    "description": "1-300. 1=D-1 (Rasi), 2=D-2 (Hora), 9=D-9 (Navamsa), and so on.",
                    "default": 1
                  },
                  "chart_method": {
                    "type": "integer",
                    "description": "1-6. Which classical construction to use for the divisional chart.",
                    "default": 1
                  },
                  "base_rasi": {
                    "type": "integer",
                    "description": "0-11 (0=Aries, 11=Pisces), for custom charts."
                  },
                  "count_from_end_of_sign": {
                    "type": "boolean",
                    "description": "Count divisions backward in even signs."
                  },
                  "varnada_method": {
                    "type": "integer",
                    "description": "Positive integer.",
                    "default": 1
                  },
                  "tamil_month_method": {
                    "type": "integer",
                    "description": "0=Ravi Annasamy, 1=V4.3.5, 2=10AM Start, 3=Midday UTC.",
                    "default": 3
                  },
                  "kali_start_year": {
                    "type": "integer",
                    "description": "1=Prabhava (South Indian) or 13=Pramaadhi (North Indian).",
                    "default": 13
                  }
                },
                "required": [
                  "date",
                  "time",
                  "place",
                  "latitude",
                  "longitude",
                  "timezone"
                ]
              },
              "example": {
                "date": "1993-07-27",
                "time": "13:51:00",
                "place": "Hassan",
                "latitude": 13,
                "longitude": 76.1,
                "timezone": 5.5
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success. Top-level keys: birth_details, horoscope.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Top-level keys: birth_details, horoscope."
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema violation, raised before the handler runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Calculation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/horoscope/pdf": {
      "post": {
        "operationId": "horoscopePdf",
        "summary": "The same chart rendered as a downloadable PDF report.",
        "description": "Runs the identical calculation path as POST /horoscope — there is no second computation and no divergence — then typesets it as a designed report and returns application/pdf with a Content-Disposition of inline; filename=\"janma-kundali.pdf\". Dosha presence and the birth panchanga are computed additionally for the cover; the /horoscope JSON response is unaffected. This is the one gated endpoint on the service.\n\nRate limit: 10 requests/minute per IP.\n\nAuthentication: X-PDF-Secret request header, compared against the server's HOROSCOPE_PDF_SECRET with a constant-time comparison. Fails closed — no secret configured on the server means every call is rejected. This endpoint is for the site's own backend, not for third-party integration.",
        "tags": [
          "Calculation"
        ],
        "security": [
          {
            "pdfSecret": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "date": {
                    "type": "string",
                    "description": "Birth date, YYYY-MM-DD. Year must be 1900-2100, month 1-12, day 1-31."
                  },
                  "time": {
                    "type": "string",
                    "description": "Birth time, HH:MM:SS (24-hour). Hour 0-23, minute 0-59, second 0-59."
                  },
                  "place": {
                    "type": "string",
                    "description": "Birth place name. Cosmetic on the transit endpoints, where it defaults to an empty string; required on /horoscope."
                  },
                  "latitude": {
                    "type": "number",
                    "description": "Decimal degrees, -90 to 90."
                  },
                  "longitude": {
                    "type": "number",
                    "description": "Decimal degrees, -180 to 180."
                  },
                  "timezone": {
                    "type": "number",
                    "description": "UTC offset in hours, -12 to 14 (5.5 for IST). The service re-resolves the historical, DST-correct offset for the birth moment, so this is a hint rather than the last word."
                  },
                  "elevation": {
                    "type": "number",
                    "description": "Metres above sea level.",
                    "default": 0
                  },
                  "ayanamsa_mode": {
                    "type": "string",
                    "description": "One of 18 honoured modes. Case-insensitive; uppercased server-side. SENTHIL, SUNDAR_SS and SIDM_USER are accepted by validation but NOT applied by the engine — SENTHIL and SUNDAR_SS silently return a LAHIRI chart while reporting their own ayanamsa_value. Do not use them.",
                    "default": "LAHIRI",
                    "enum": [
                      "LAHIRI",
                      "TRUE_CITRA",
                      "TRUE_LAHIRI",
                      "KP",
                      "FAGAN",
                      "RAMAN",
                      "USHASHASHI",
                      "YUKTESHWAR",
                      "SURYASIDDHANTA",
                      "SURYASIDDHANTA_MSUN",
                      "ARYABHATA",
                      "ARYABHATA_MSUN",
                      "SS_CITRA",
                      "TRUE_REVATI",
                      "SS_REVATI",
                      "TRUE_PUSHYA",
                      "TRUE_MULA",
                      "KP-SENTHIL"
                    ]
                  },
                  "ayanamsa_value": {
                    "type": "number",
                    "description": "Custom ayanamsa in degrees, -30.0 to 30.0. Only meaningful with SIDM_USER."
                  },
                  "calculation_type": {
                    "type": "string",
                    "description": "\"drik\" (modern) or \"ss\" (Surya Siddhanta). Case-insensitive.",
                    "default": "drik"
                  },
                  "node_type": {
                    "type": "string",
                    "description": "Rahu/Ketu node: \"mean\" or \"true\".",
                    "default": "mean"
                  },
                  "bhava_madhya_method": {
                    "type": "integer",
                    "description": "House system, 1-5: 1=Equal-Lagna-Middle, 2=Equal-Lagna-Start, 3=Sripati, 4=KP, 5=Whole-Sign.",
                    "default": 1
                  },
                  "language": {
                    "type": "string",
                    "description": "One of en, hi, ka, ta, te.",
                    "default": "en"
                  },
                  "pravesha_type": {
                    "type": "integer",
                    "description": "0=Natal, 1=Annual, 2=Tithi-Pravesha.",
                    "default": 0
                  },
                  "years": {
                    "type": "integer",
                    "description": "Year number for annual/progressed charts, 1-120.",
                    "default": 1
                  },
                  "months": {
                    "type": "integer",
                    "description": "Month number for monthly charts, 1-12.",
                    "default": 1
                  },
                  "sixty_hours": {
                    "type": "integer",
                    "description": "60-hour period for hora progressions. The validator enforces 1-1000.",
                    "default": 1
                  },
                  "divisional_chart_factor": {
                    "type": "integer",
                    "description": "1-300. 1=D-1 (Rasi), 2=D-2 (Hora), 9=D-9 (Navamsa), and so on.",
                    "default": 1
                  },
                  "chart_method": {
                    "type": "integer",
                    "description": "1-6. Which classical construction to use for the divisional chart.",
                    "default": 1
                  },
                  "base_rasi": {
                    "type": "integer",
                    "description": "0-11 (0=Aries, 11=Pisces), for custom charts."
                  },
                  "count_from_end_of_sign": {
                    "type": "boolean",
                    "description": "Count divisions backward in even signs."
                  },
                  "varnada_method": {
                    "type": "integer",
                    "description": "Positive integer.",
                    "default": 1
                  },
                  "tamil_month_method": {
                    "type": "integer",
                    "description": "0=Ravi Annasamy, 1=V4.3.5, 2=10AM Start, 3=Midday UTC.",
                    "default": 3
                  },
                  "kali_start_year": {
                    "type": "integer",
                    "description": "1=Prabhava (South Indian) or 13=Pramaadhi (North Indian).",
                    "default": 13
                  },
                  "name": {
                    "type": "string",
                    "description": "Name printed on the cover. Maximum 80 characters."
                  },
                  "chart_style": {
                    "type": "string",
                    "description": "\"north\" or \"south\". Anything else is rejected.",
                    "default": "north"
                  },
                  "photo": {
                    "type": "string",
                    "description": "Base64 cover photo; validated and re-encoded server-side."
                  },
                  "online_url": {
                    "type": "string",
                    "description": "Maximum 300 characters. Renders a \"View online\" button at the top of the PDF."
                  }
                },
                "required": [
                  "date",
                  "time",
                  "place",
                  "latitude",
                  "longitude",
                  "timezone"
                ]
              },
              "example": {
                "date": "1993-07-27",
                "time": "13:51:00",
                "place": "Hassan",
                "latitude": 13,
                "longitude": 76.1,
                "timezone": 5.5,
                "name": "Asha R",
                "chart_style": "south"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success. Top-level keys: (binary application/pdf).",
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Missing or wrong shared secret.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema violation, raised before the handler runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Calculation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/marriage-match": {
      "post": {
        "operationId": "marriageMatch",
        "summary": "Ashtakoota (North Indian) and 10 Porutham (South Indian) compatibility for two charts.",
        "description": "Takes two birth charts and scores them under both classical systems at once, then adds the Manglik-family dosha load for each person and the Beeja/Kshetra sphutas. The North Indian block scores the eight kutas out of 36; the South Indian block reports each of the ten poruthams as matched or not, with the minimum-porutham flag. It returns the computation, not a verdict.\n\nRate limit: Inherits the app default (200 requests/minute per IP) — this endpoint declares no limiter of its own.",
        "tags": [
          "Calculation"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "boy_birth_details": {
                    "type": "object",
                    "description": "date, time, latitude, longitude, timezone (all required) and elevation (default 0.0). Note this nested model has NO place field and takes no calculation settings."
                  },
                  "girl_birth_details": {
                    "type": "object",
                    "description": "Same shape as boy_birth_details."
                  }
                },
                "required": [
                  "boy_birth_details",
                  "girl_birth_details"
                ]
              },
              "example": {
                "boy_birth_details": {
                  "date": "1993-07-27",
                  "time": "13:51:00",
                  "latitude": 13,
                  "longitude": 76.1,
                  "timezone": 5.5
                },
                "girl_birth_details": {
                  "date": "1995-11-02",
                  "time": "06:20:00",
                  "latitude": 12.9716,
                  "longitude": 77.5946,
                  "timezone": 5.5
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success. Top-level keys: boy, girl, north_indian, south_indian, boy_dosha_data, girl_dosha_data, beeja_rasi, kshetra_rasi.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Top-level keys: boy, girl, north_indian, south_indian, boy_dosha_data, girl_dosha_data, beeja_rasi, kshetra_rasi."
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema violation, raised before the handler runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Calculation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/panchang": {
      "post": {
        "operationId": "panchang",
        "summary": "The full daily panchang for a date and place. No birth chart needed.",
        "description": "The five limbs — vara, tithi, nakshatra, yoga, karana — plus sun and moon rise/set, day length, the auspicious and inauspicious muhurta windows, day and night Choghadiya, the lunar month in both Amanta and Purnimanta reckoning, ritu, ayana, Shaka and Vikram Samvat with the sixty-year cycle name, and the Moon and Sun signs. Deterministic for a given date and place; the response carries Cache-Control: public, max-age=21600.\n\nRate limit: 60 requests/minute per IP.",
        "tags": [
          "Calculation"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "date": {
                    "type": "string",
                    "description": "YYYY-MM-DD. Rejects dates that do not exist on the calendar, e.g. 2026-02-31."
                  },
                  "latitude": {
                    "type": "number",
                    "description": "-90 to 90, and additionally |latitude| must be 66 or less — panchang is sunrise-anchored and undefined beyond the polar circles."
                  },
                  "longitude": {
                    "type": "number",
                    "description": "-180 to 180."
                  },
                  "timezone": {
                    "type": "number",
                    "description": "UTC offset, -12 to 14."
                  },
                  "place": {
                    "type": "string",
                    "description": "Cosmetic.",
                    "default": ""
                  },
                  "ayanamsa_mode": {
                    "type": "string",
                    "description": "Any allowed ayanamsa mode.",
                    "default": "LAHIRI"
                  }
                },
                "required": [
                  "date",
                  "latitude",
                  "longitude",
                  "timezone"
                ]
              },
              "example": {
                "date": "2026-07-13",
                "latitude": 28.6139,
                "longitude": 77.209,
                "timezone": 5.5,
                "place": "New Delhi"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success. Top-level keys: panchang.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Top-level keys: panchang."
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema violation, raised before the handler runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Calculation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/gochar": {
      "post": {
        "operationId": "gochar",
        "summary": "Live planetary transits for a date and place. Birthless.",
        "description": "Where the natal chart is irrelevant: the nine grahas with sign and degree, nakshatra and pada, direct or retrograde motion, combustion flag, classical dignity (exalted, debilitated, moolatrikona, own), daily speed, and the date each next changes sign, nakshatra and pada — plus the Moon and Sun signs and the set of planets currently retrograde. Positions are geocentric and therefore identical worldwide at an instant; the place's timezone governs only what counts as \"today\" and how the dates are displayed. Cached 6 hours.\n\nRate limit: 60 requests/minute per IP.",
        "tags": [
          "Calculation"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "date": {
                    "type": "string",
                    "description": "YYYY-MM-DD; must be a real calendar date."
                  },
                  "latitude": {
                    "type": "number",
                    "description": "-90 to 90. No polar restriction — gochar is sunrise-free and well defined at every latitude."
                  },
                  "longitude": {
                    "type": "number",
                    "description": "-180 to 180."
                  },
                  "timezone": {
                    "type": "number",
                    "description": "UTC offset, -12 to 14."
                  },
                  "place": {
                    "type": "string",
                    "description": "Cosmetic.",
                    "default": ""
                  },
                  "ayanamsa_mode": {
                    "type": "string",
                    "description": "Any allowed ayanamsa mode.",
                    "default": "LAHIRI"
                  }
                },
                "required": [
                  "date",
                  "latitude",
                  "longitude",
                  "timezone"
                ]
              },
              "example": {
                "date": "2026-07-14",
                "latitude": 28.6139,
                "longitude": 77.209,
                "timezone": 5.5,
                "place": "New Delhi"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success. Top-level keys: gochar.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Top-level keys: gochar."
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema violation, raised before the handler runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Calculation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/gochar/events": {
      "post": {
        "operationId": "gocharEvents",
        "summary": "Upcoming transit events over a date range. Birthless.",
        "description": "One merged, date-sorted list of sign changes for every requested planet, nakshatra changes for all of them except the Moon (which changes nakshatra roughly daily and would drown the list), and retrograde stations for Mars, Mercury, Jupiter, Venus and Saturn. Event dates are expressed in the requested timezone. Computed fresh.\n\nRate limit: 60 requests/minute per IP.",
        "tags": [
          "Calculation"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "from_date": {
                    "type": "string",
                    "description": "YYYY-MM-DD, a real calendar date."
                  },
                  "to_date": {
                    "type": "string",
                    "description": "YYYY-MM-DD. The range is capped at 90 days."
                  },
                  "latitude": {
                    "type": "number",
                    "description": "-90 to 90. Used for the display timezone."
                  },
                  "longitude": {
                    "type": "number",
                    "description": "-180 to 180."
                  },
                  "timezone": {
                    "type": "number",
                    "description": "UTC offset used to express event dates, -12 to 14."
                  },
                  "place": {
                    "type": "string",
                    "description": "Cosmetic.",
                    "default": ""
                  },
                  "planets": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Planet names. Defaults to all nine: Sun, Moon, Mars, Mercury, Jupiter, Venus, Saturn, Rahu, Ketu."
                  },
                  "ayanamsa_mode": {
                    "type": "string",
                    "description": "Any allowed ayanamsa mode.",
                    "default": "LAHIRI"
                  }
                },
                "required": [
                  "from_date",
                  "to_date",
                  "latitude",
                  "longitude",
                  "timezone"
                ]
              },
              "example": {
                "from_date": "2026-07-14",
                "to_date": "2026-08-13",
                "latitude": 28.6139,
                "longitude": 77.209,
                "timezone": 5.5,
                "planets": [
                  "Jupiter",
                  "Saturn",
                  "Mercury"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success. Top-level keys: gochar_events.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Top-level keys: gochar_events."
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema violation, raised before the handler runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Calculation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/gochara": {
      "post": {
        "operationId": "gochara",
        "summary": "A transit snapshot read against a natal chart, for one target date.",
        "description": "The natal counterpart to /gochar. Returns the panchanga for the target moment; the muhurta windows (sunrise, sunset, solar noon, Abhijit, and the Rahu, Gulika and Yamaganda periods to avoid); all nine planets with sign, degree, nakshatra and pada, retrograde and combust flags, and their house from both the natal Moon and the natal Lagna; and the person-relative strengths — Tara Bala from Moon, Sun and Lagna, Chandra Bala, and Sade Sati state. The event location is optional and defaults to the birth location. Computed fresh, no cache.\n\nRate limit: 60 requests/minute per IP.",
        "tags": [
          "Calculation"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "date": {
                    "type": "string",
                    "description": "Birth date, YYYY-MM-DD. Year must be 1900-2100, month 1-12, day 1-31."
                  },
                  "time": {
                    "type": "string",
                    "description": "Birth time, HH:MM:SS (24-hour). Hour 0-23, minute 0-59, second 0-59."
                  },
                  "place": {
                    "type": "string",
                    "description": "Birth place name. Cosmetic on the transit endpoints, where it defaults to an empty string; required on /horoscope."
                  },
                  "latitude": {
                    "type": "number",
                    "description": "Decimal degrees, -90 to 90."
                  },
                  "longitude": {
                    "type": "number",
                    "description": "Decimal degrees, -180 to 180."
                  },
                  "timezone": {
                    "type": "number",
                    "description": "UTC offset in hours, -12 to 14 (5.5 for IST). The service re-resolves the historical, DST-correct offset for the birth moment, so this is a hint rather than the last word."
                  },
                  "elevation": {
                    "type": "number",
                    "description": "Metres above sea level.",
                    "default": 0
                  },
                  "ayanamsa_mode": {
                    "type": "string",
                    "description": "One of 18 honoured modes. Case-insensitive; uppercased server-side. SENTHIL, SUNDAR_SS and SIDM_USER are accepted by validation but NOT applied by the engine — SENTHIL and SUNDAR_SS silently return a LAHIRI chart while reporting their own ayanamsa_value. Do not use them.",
                    "default": "LAHIRI",
                    "enum": [
                      "LAHIRI",
                      "TRUE_CITRA",
                      "TRUE_LAHIRI",
                      "KP",
                      "FAGAN",
                      "RAMAN",
                      "USHASHASHI",
                      "YUKTESHWAR",
                      "SURYASIDDHANTA",
                      "SURYASIDDHANTA_MSUN",
                      "ARYABHATA",
                      "ARYABHATA_MSUN",
                      "SS_CITRA",
                      "TRUE_REVATI",
                      "SS_REVATI",
                      "TRUE_PUSHYA",
                      "TRUE_MULA",
                      "KP-SENTHIL"
                    ]
                  },
                  "ayanamsa_value": {
                    "type": "number",
                    "description": "Custom ayanamsa in degrees, -30.0 to 30.0. Only meaningful with SIDM_USER."
                  },
                  "calculation_type": {
                    "type": "string",
                    "description": "\"drik\" (modern) or \"ss\" (Surya Siddhanta). Case-insensitive.",
                    "default": "drik"
                  },
                  "node_type": {
                    "type": "string",
                    "description": "Rahu/Ketu node: \"mean\" or \"true\".",
                    "default": "mean"
                  },
                  "bhava_madhya_method": {
                    "type": "integer",
                    "description": "House system, 1-5: 1=Equal-Lagna-Middle, 2=Equal-Lagna-Start, 3=Sripati, 4=KP, 5=Whole-Sign.",
                    "default": 1
                  },
                  "language": {
                    "type": "string",
                    "description": "One of en, hi, ka, ta, te.",
                    "default": "en"
                  },
                  "pravesha_type": {
                    "type": "integer",
                    "description": "0=Natal, 1=Annual, 2=Tithi-Pravesha.",
                    "default": 0
                  },
                  "years": {
                    "type": "integer",
                    "description": "Year number for annual/progressed charts, 1-120.",
                    "default": 1
                  },
                  "months": {
                    "type": "integer",
                    "description": "Month number for monthly charts, 1-12.",
                    "default": 1
                  },
                  "sixty_hours": {
                    "type": "integer",
                    "description": "60-hour period for hora progressions. The validator enforces 1-1000.",
                    "default": 1
                  },
                  "divisional_chart_factor": {
                    "type": "integer",
                    "description": "1-300. 1=D-1 (Rasi), 2=D-2 (Hora), 9=D-9 (Navamsa), and so on.",
                    "default": 1
                  },
                  "chart_method": {
                    "type": "integer",
                    "description": "1-6. Which classical construction to use for the divisional chart.",
                    "default": 1
                  },
                  "base_rasi": {
                    "type": "integer",
                    "description": "0-11 (0=Aries, 11=Pisces), for custom charts."
                  },
                  "count_from_end_of_sign": {
                    "type": "boolean",
                    "description": "Count divisions backward in even signs."
                  },
                  "varnada_method": {
                    "type": "integer",
                    "description": "Positive integer.",
                    "default": 1
                  },
                  "tamil_month_method": {
                    "type": "integer",
                    "description": "0=Ravi Annasamy, 1=V4.3.5, 2=10AM Start, 3=Midday UTC.",
                    "default": 3
                  },
                  "kali_start_year": {
                    "type": "integer",
                    "description": "1=Prabhava (South Indian) or 13=Pramaadhi (North Indian).",
                    "default": 13
                  },
                  "target_date": {
                    "type": "string",
                    "description": "YYYY-MM-DD — the transit moment."
                  },
                  "target_time": {
                    "type": "string",
                    "description": "HH:MM:SS. Defaults to local noon.",
                    "default": "12:00:00"
                  },
                  "event_place": {
                    "type": "string",
                    "description": "Falls back to the birth place."
                  },
                  "event_latitude": {
                    "type": "number",
                    "description": "-90 to 90. Falls back to the birth latitude."
                  },
                  "event_longitude": {
                    "type": "number",
                    "description": "-180 to 180. Falls back to the birth longitude."
                  },
                  "event_timezone": {
                    "type": "number",
                    "description": "-12 to 14. Falls back to the birth timezone."
                  },
                  "event_elevation": {
                    "type": "number",
                    "description": "Metres. Falls back to the birth elevation."
                  },
                  "include": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Enrichment sections. \"ashtakavarga\" is the recognised value; omit the field for the default, which includes it."
                  }
                },
                "required": [
                  "date",
                  "time",
                  "place",
                  "latitude",
                  "longitude",
                  "timezone",
                  "target_date"
                ]
              },
              "example": {
                "date": "1991-08-18",
                "time": "11:13:00",
                "place": "Hassan",
                "latitude": 13.0071,
                "longitude": 76.0962,
                "timezone": 5.5,
                "target_date": "2026-06-25",
                "event_place": "Bangalore",
                "event_latitude": 12.9716,
                "event_longitude": 77.5946
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success. Top-level keys: birth_details, gochara.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Top-level keys: birth_details, gochara."
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema violation, raised before the handler runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Calculation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/gochara/range": {
      "post": {
        "operationId": "gocharaRange",
        "summary": "The same natal transit snapshot for every day in a range, in one call.",
        "description": "Built for the case where an AI agent should do the deciding. It returns the full per-day gochara detail — panchanga, muhurta windows including a clean Abhijit, planets, person-relative strength, ashtakavarga, gochara-phala and vedha — with no scoring and no \"best date\" verdict, so the caller reasons over the raw data itself. The natal reference is computed once and reused across the range. Computed fresh; the range is capped at 60 days.\n\nRate limit: 30 requests/minute per IP.",
        "tags": [
          "Calculation"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "date": {
                    "type": "string",
                    "description": "Birth date, YYYY-MM-DD. Year must be 1900-2100, month 1-12, day 1-31."
                  },
                  "time": {
                    "type": "string",
                    "description": "Birth time, HH:MM:SS (24-hour). Hour 0-23, minute 0-59, second 0-59."
                  },
                  "place": {
                    "type": "string",
                    "description": "Birth place name. Cosmetic on the transit endpoints, where it defaults to an empty string; required on /horoscope."
                  },
                  "latitude": {
                    "type": "number",
                    "description": "Decimal degrees, -90 to 90."
                  },
                  "longitude": {
                    "type": "number",
                    "description": "Decimal degrees, -180 to 180."
                  },
                  "timezone": {
                    "type": "number",
                    "description": "UTC offset in hours, -12 to 14 (5.5 for IST). The service re-resolves the historical, DST-correct offset for the birth moment, so this is a hint rather than the last word."
                  },
                  "elevation": {
                    "type": "number",
                    "description": "Metres above sea level.",
                    "default": 0
                  },
                  "ayanamsa_mode": {
                    "type": "string",
                    "description": "One of 18 honoured modes. Case-insensitive; uppercased server-side. SENTHIL, SUNDAR_SS and SIDM_USER are accepted by validation but NOT applied by the engine — SENTHIL and SUNDAR_SS silently return a LAHIRI chart while reporting their own ayanamsa_value. Do not use them.",
                    "default": "LAHIRI",
                    "enum": [
                      "LAHIRI",
                      "TRUE_CITRA",
                      "TRUE_LAHIRI",
                      "KP",
                      "FAGAN",
                      "RAMAN",
                      "USHASHASHI",
                      "YUKTESHWAR",
                      "SURYASIDDHANTA",
                      "SURYASIDDHANTA_MSUN",
                      "ARYABHATA",
                      "ARYABHATA_MSUN",
                      "SS_CITRA",
                      "TRUE_REVATI",
                      "SS_REVATI",
                      "TRUE_PUSHYA",
                      "TRUE_MULA",
                      "KP-SENTHIL"
                    ]
                  },
                  "ayanamsa_value": {
                    "type": "number",
                    "description": "Custom ayanamsa in degrees, -30.0 to 30.0. Only meaningful with SIDM_USER."
                  },
                  "calculation_type": {
                    "type": "string",
                    "description": "\"drik\" (modern) or \"ss\" (Surya Siddhanta). Case-insensitive.",
                    "default": "drik"
                  },
                  "node_type": {
                    "type": "string",
                    "description": "Rahu/Ketu node: \"mean\" or \"true\".",
                    "default": "mean"
                  },
                  "bhava_madhya_method": {
                    "type": "integer",
                    "description": "House system, 1-5: 1=Equal-Lagna-Middle, 2=Equal-Lagna-Start, 3=Sripati, 4=KP, 5=Whole-Sign.",
                    "default": 1
                  },
                  "language": {
                    "type": "string",
                    "description": "One of en, hi, ka, ta, te.",
                    "default": "en"
                  },
                  "pravesha_type": {
                    "type": "integer",
                    "description": "0=Natal, 1=Annual, 2=Tithi-Pravesha.",
                    "default": 0
                  },
                  "years": {
                    "type": "integer",
                    "description": "Year number for annual/progressed charts, 1-120.",
                    "default": 1
                  },
                  "months": {
                    "type": "integer",
                    "description": "Month number for monthly charts, 1-12.",
                    "default": 1
                  },
                  "sixty_hours": {
                    "type": "integer",
                    "description": "60-hour period for hora progressions. The validator enforces 1-1000.",
                    "default": 1
                  },
                  "divisional_chart_factor": {
                    "type": "integer",
                    "description": "1-300. 1=D-1 (Rasi), 2=D-2 (Hora), 9=D-9 (Navamsa), and so on.",
                    "default": 1
                  },
                  "chart_method": {
                    "type": "integer",
                    "description": "1-6. Which classical construction to use for the divisional chart.",
                    "default": 1
                  },
                  "base_rasi": {
                    "type": "integer",
                    "description": "0-11 (0=Aries, 11=Pisces), for custom charts."
                  },
                  "count_from_end_of_sign": {
                    "type": "boolean",
                    "description": "Count divisions backward in even signs."
                  },
                  "varnada_method": {
                    "type": "integer",
                    "description": "Positive integer.",
                    "default": 1
                  },
                  "tamil_month_method": {
                    "type": "integer",
                    "description": "0=Ravi Annasamy, 1=V4.3.5, 2=10AM Start, 3=Midday UTC.",
                    "default": 3
                  },
                  "kali_start_year": {
                    "type": "integer",
                    "description": "1=Prabhava (South Indian) or 13=Pramaadhi (North Indian).",
                    "default": 13
                  },
                  "from_date": {
                    "type": "string",
                    "description": "YYYY-MM-DD."
                  },
                  "to_date": {
                    "type": "string",
                    "description": "YYYY-MM-DD. Maximum 60 days from from_date."
                  },
                  "time_of_day": {
                    "type": "string",
                    "description": "HH:MM:SS used for each day. Defaults to local noon.",
                    "default": "12:00:00"
                  },
                  "event_place": {
                    "type": "string",
                    "description": "Falls back to the birth place."
                  },
                  "event_latitude": {
                    "type": "number",
                    "description": "Falls back to the birth latitude."
                  },
                  "event_longitude": {
                    "type": "number",
                    "description": "Falls back to the birth longitude."
                  },
                  "event_timezone": {
                    "type": "number",
                    "description": "Falls back to the birth timezone."
                  },
                  "event_elevation": {
                    "type": "number",
                    "description": "Falls back to the birth elevation."
                  },
                  "include": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Enrichment sections; the documented values are \"ashtakavarga\" and \"gochara_phala\". Omit for all of them, or pass [] for the lean snapshot."
                  }
                },
                "required": [
                  "date",
                  "time",
                  "place",
                  "latitude",
                  "longitude",
                  "timezone",
                  "from_date",
                  "to_date"
                ]
              },
              "example": {
                "date": "1991-08-18",
                "time": "11:13:00",
                "place": "Hassan",
                "latitude": 13.0071,
                "longitude": 76.0962,
                "timezone": 5.5,
                "from_date": "2026-06-24",
                "to_date": "2026-06-30",
                "include": []
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success. Top-level keys: gochara_range.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Top-level keys: gochara_range."
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema violation, raised before the handler runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Calculation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/saturn-timeline": {
      "post": {
        "operationId": "saturnTimeline",
        "summary": "Sade Sati and Dhaiyya timing, with month-by-month pressure and relief bands.",
        "description": "Returns the sign-based Sade Sati cycles for a chart, whether one is running right now and which of the three phases it is in, how many months remain, and — derived from Saturn's kakshya in the Ashtakavarga — the classical relief share. detail=\"full\" adds the month-by-month bands, the interruptions and the segments. This is computation, not interpretation: dates and supported/unsupported flags, nothing else.\n\nRate limit: 60 requests/minute per IP.",
        "tags": [
          "Calculation"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "date": {
                    "type": "string",
                    "description": "Birth date, YYYY-MM-DD."
                  },
                  "time": {
                    "type": "string",
                    "description": "Birth time, HH:MM:SS."
                  },
                  "place": {
                    "type": "string",
                    "description": "Cosmetic.",
                    "default": ""
                  },
                  "latitude": {
                    "type": "number",
                    "description": "-90 to 90."
                  },
                  "longitude": {
                    "type": "number",
                    "description": "-180 to 180."
                  },
                  "timezone": {
                    "type": "number",
                    "description": "UTC offset, -12 to 14."
                  },
                  "ayanamsa_mode": {
                    "type": "string",
                    "description": "ONLY \"LAHIRI\" or \"RAMAN\" here — narrower than every other endpoint. The two subsystems this endpoint drives honour only those two labels identically; anything else would silently compute a mismatched chart, so it is rejected with a 400 instead.",
                    "default": "LAHIRI"
                  },
                  "detail": {
                    "type": "string",
                    "description": "\"summary\" or \"full\".",
                    "default": "summary"
                  }
                },
                "required": [
                  "date",
                  "time",
                  "latitude",
                  "longitude",
                  "timezone"
                ]
              },
              "example": {
                "date": "1990-05-15",
                "time": "14:30:00",
                "place": "Bangalore",
                "latitude": 12.9716,
                "longitude": 77.5946,
                "timezone": 5.5,
                "detail": "summary"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success. Top-level keys: saturn_timeline.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Top-level keys: saturn_timeline."
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema violation, raised before the handler runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Calculation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/planet-ingress": {
      "post": {
        "operationId": "planetIngress",
        "summary": "Every sidereal sign change for a set of planets across a date range.",
        "description": "Includes retrograde back-outs and re-entries, each crossing flagged so you can tell a temporary ingress from the one that sticks: a final_settling flag marks the last crossing of each sign boundary inside the window. Data only, computed fresh.\n\nRate limit: 60 requests/minute per IP.",
        "tags": [
          "Calculation"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "planets": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    },
                    "description": "Planet names. Defaults to the slow movers: Saturn, Jupiter, Rahu, Ketu."
                  },
                  "from_date": {
                    "type": "string",
                    "description": "YYYY-MM-DD."
                  },
                  "to_date": {
                    "type": "string",
                    "description": "YYYY-MM-DD. The field description states a maximum of about 5 years."
                  },
                  "timezone": {
                    "type": "number",
                    "description": "UTC offset used to express the crossing dates, -12 to 14. Defaults to UTC, not to IST.",
                    "default": 0
                  },
                  "ayanamsa_mode": {
                    "type": "string",
                    "description": "Any allowed ayanamsa mode.",
                    "default": "LAHIRI"
                  }
                },
                "required": [
                  "from_date",
                  "to_date"
                ]
              },
              "example": {
                "planets": [
                  "Saturn",
                  "Jupiter"
                ],
                "from_date": "2025-01-01",
                "to_date": "2027-12-31",
                "timezone": 5.5
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success. Top-level keys: planet_ingress.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Top-level keys: planet_ingress."
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema violation, raised before the handler runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Calculation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/muhurta": {
      "post": {
        "operationId": "muhurta",
        "summary": "Score every day in a range for an event, against classical muhurta rules and the native's own chart.",
        "description": "Scores each day by the classical factors — vara, nakshatra, tithi, paksha — and by the person's strength on that day: Tara Bala, Chandra Bala and Sade Sati state, per the rule table for the chosen event type. It returns the per-date factor data, a deterministic score with its breakdown and the weights used, and the auspicious Abhijit window plus the Rahu, Gulika and Yamaganda windows to avoid. It does not pick a date; the caller composes the recommendation. Computed fresh.\n\nRate limit: 30 requests/minute per IP.",
        "tags": [
          "Calculation"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "date": {
                    "type": "string",
                    "description": "Birth date, YYYY-MM-DD. Year must be 1900-2100, month 1-12, day 1-31."
                  },
                  "time": {
                    "type": "string",
                    "description": "Birth time, HH:MM:SS (24-hour). Hour 0-23, minute 0-59, second 0-59."
                  },
                  "place": {
                    "type": "string",
                    "description": "Birth place name. Cosmetic on the transit endpoints, where it defaults to an empty string; required on /horoscope."
                  },
                  "latitude": {
                    "type": "number",
                    "description": "Decimal degrees, -90 to 90."
                  },
                  "longitude": {
                    "type": "number",
                    "description": "Decimal degrees, -180 to 180."
                  },
                  "timezone": {
                    "type": "number",
                    "description": "UTC offset in hours, -12 to 14 (5.5 for IST). The service re-resolves the historical, DST-correct offset for the birth moment, so this is a hint rather than the last word."
                  },
                  "elevation": {
                    "type": "number",
                    "description": "Metres above sea level.",
                    "default": 0
                  },
                  "ayanamsa_mode": {
                    "type": "string",
                    "description": "One of 18 honoured modes. Case-insensitive; uppercased server-side. SENTHIL, SUNDAR_SS and SIDM_USER are accepted by validation but NOT applied by the engine — SENTHIL and SUNDAR_SS silently return a LAHIRI chart while reporting their own ayanamsa_value. Do not use them.",
                    "default": "LAHIRI",
                    "enum": [
                      "LAHIRI",
                      "TRUE_CITRA",
                      "TRUE_LAHIRI",
                      "KP",
                      "FAGAN",
                      "RAMAN",
                      "USHASHASHI",
                      "YUKTESHWAR",
                      "SURYASIDDHANTA",
                      "SURYASIDDHANTA_MSUN",
                      "ARYABHATA",
                      "ARYABHATA_MSUN",
                      "SS_CITRA",
                      "TRUE_REVATI",
                      "SS_REVATI",
                      "TRUE_PUSHYA",
                      "TRUE_MULA",
                      "KP-SENTHIL"
                    ]
                  },
                  "ayanamsa_value": {
                    "type": "number",
                    "description": "Custom ayanamsa in degrees, -30.0 to 30.0. Only meaningful with SIDM_USER."
                  },
                  "calculation_type": {
                    "type": "string",
                    "description": "\"drik\" (modern) or \"ss\" (Surya Siddhanta). Case-insensitive.",
                    "default": "drik"
                  },
                  "node_type": {
                    "type": "string",
                    "description": "Rahu/Ketu node: \"mean\" or \"true\".",
                    "default": "mean"
                  },
                  "bhava_madhya_method": {
                    "type": "integer",
                    "description": "House system, 1-5: 1=Equal-Lagna-Middle, 2=Equal-Lagna-Start, 3=Sripati, 4=KP, 5=Whole-Sign.",
                    "default": 1
                  },
                  "language": {
                    "type": "string",
                    "description": "One of en, hi, ka, ta, te.",
                    "default": "en"
                  },
                  "pravesha_type": {
                    "type": "integer",
                    "description": "0=Natal, 1=Annual, 2=Tithi-Pravesha.",
                    "default": 0
                  },
                  "years": {
                    "type": "integer",
                    "description": "Year number for annual/progressed charts, 1-120.",
                    "default": 1
                  },
                  "months": {
                    "type": "integer",
                    "description": "Month number for monthly charts, 1-12.",
                    "default": 1
                  },
                  "sixty_hours": {
                    "type": "integer",
                    "description": "60-hour period for hora progressions. The validator enforces 1-1000.",
                    "default": 1
                  },
                  "divisional_chart_factor": {
                    "type": "integer",
                    "description": "1-300. 1=D-1 (Rasi), 2=D-2 (Hora), 9=D-9 (Navamsa), and so on.",
                    "default": 1
                  },
                  "chart_method": {
                    "type": "integer",
                    "description": "1-6. Which classical construction to use for the divisional chart.",
                    "default": 1
                  },
                  "base_rasi": {
                    "type": "integer",
                    "description": "0-11 (0=Aries, 11=Pisces), for custom charts."
                  },
                  "count_from_end_of_sign": {
                    "type": "boolean",
                    "description": "Count divisions backward in even signs."
                  },
                  "varnada_method": {
                    "type": "integer",
                    "description": "Positive integer.",
                    "default": 1
                  },
                  "tamil_month_method": {
                    "type": "integer",
                    "description": "0=Ravi Annasamy, 1=V4.3.5, 2=10AM Start, 3=Midday UTC.",
                    "default": 3
                  },
                  "kali_start_year": {
                    "type": "integer",
                    "description": "1=Prabhava (South Indian) or 13=Pramaadhi (North Indian).",
                    "default": 13
                  },
                  "event_type": {
                    "type": "string",
                    "description": "Must be one of the tier-1 event types. Fetch the list from GET /muhurta/events; an unknown value is a 400 that names the valid set."
                  },
                  "from_date": {
                    "type": "string",
                    "description": "Scan start, YYYY-MM-DD."
                  },
                  "to_date": {
                    "type": "string",
                    "description": "Scan end, YYYY-MM-DD. Maximum 90 days."
                  },
                  "event_place": {
                    "type": "string",
                    "description": "Falls back to the birth place."
                  },
                  "event_latitude": {
                    "type": "number",
                    "description": "Falls back to the birth latitude."
                  },
                  "event_longitude": {
                    "type": "number",
                    "description": "Falls back to the birth longitude."
                  },
                  "event_timezone": {
                    "type": "number",
                    "description": "Falls back to the birth timezone."
                  },
                  "event_elevation": {
                    "type": "number",
                    "description": "Falls back to the birth elevation."
                  },
                  "day_part_resolution_minutes": {
                    "type": "integer",
                    "description": "Time-of-day granularity, 5 to 60 minutes.",
                    "default": 15
                  },
                  "respect_inauspicious_windows": {
                    "type": "boolean",
                    "description": "Report the Rahu, Gulika and Yamaganda windows to avoid.",
                    "default": true
                  }
                },
                "required": [
                  "date",
                  "time",
                  "place",
                  "latitude",
                  "longitude",
                  "timezone",
                  "event_type",
                  "from_date",
                  "to_date"
                ]
              },
              "example": {
                "date": "1991-08-18",
                "time": "11:13:00",
                "place": "Hassan",
                "latitude": 13.0071,
                "longitude": 76.0962,
                "timezone": 5.5,
                "event_type": "griha_pravesha_rented",
                "from_date": "2026-06-24",
                "to_date": "2026-06-30",
                "event_place": "Bangalore",
                "event_latitude": 12.9716,
                "event_longitude": 77.5946
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Success. Top-level keys: muhurta.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Top-level keys: muhurta."
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema violation, raised before the handler runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Calculation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/muhurta/events": {
      "get": {
        "operationId": "muhurtaEvents",
        "summary": "The event types POST /muhurta accepts, with their rule sources.",
        "description": "Call this first. It is the authoritative list of event_type values; sending anything not in it is a 400. Each entry carries a human label and the classical source the rule table came from.\n\nRate limit: Inherits the app default (200 requests/minute per IP).",
        "tags": [
          "Lookup"
        ],
        "responses": {
          "200": {
            "description": "Success. Top-level keys: events.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Top-level keys: events."
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema violation, raised before the handler runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Calculation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/location/search": {
      "get": {
        "operationId": "locationSearch",
        "summary": "Resolve a place name to coordinates and a timezone offset.",
        "description": "Resolves against a local index of 162,000+ world cities — instant, free, offline, and deterministic. No external geocoder is contacted. The single best match is returned. For \"City, Country\" inputs that do not match directly, the trailing qualifier is dropped and retried, so \"Springfield, IL, US\" falls back to \"Springfield, IL\". Use this to turn a user's typed birth place into the latitude, longitude and timezone the calculation endpoints need.\n\nRate limit: Inherits the app default (200 requests/minute per IP).",
        "tags": [
          "Lookup"
        ],
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": true,
            "description": "The place name. Minimum 2 characters after trimming, or 400.",
            "schema": {
              "type": "string",
              "description": "The place name. Minimum 2 characters after trimming, or 400."
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Alias for query, accepted because external callers commonly send ?q=. If both are present, query wins.",
            "schema": {
              "type": "string",
              "description": "Alias for query, accepted because external callers commonly send ?q=. If both are present, query wins."
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success. Top-level keys: place, latitude, longitude, timezone, source, country, displayName, state (when known).",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Top-level keys: place, latitude, longitude, timezone, source, country, displayName, state (when known)."
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No location matched.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema violation, raised before the handler runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Calculation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/location/autocomplete": {
      "get": {
        "operationId": "locationAutocomplete",
        "summary": "Type-ahead suggestions from the same 162,000-city index.",
        "description": "Substring search over the local city index, for populating a birth-place picker as the user types. Much faster than an external geocoding API and free.\n\nRate limit: Inherits the app default (200 requests/minute per IP).",
        "tags": [
          "Lookup"
        ],
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "required": true,
            "description": "Partial place name. Minimum 2 characters, or 400.",
            "schema": {
              "type": "string",
              "description": "Partial place name. Minimum 2 characters, or 400."
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Alias for query.",
            "schema": {
              "type": "string",
              "description": "Alias for query."
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum results. Values above 50 are clamped to 50 rather than rejected.",
            "schema": {
              "type": "integer",
              "description": "Maximum results. Values above 50 are clamped to 50 rather than rejected.",
              "default": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Success. Top-level keys: query, count, results.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Top-level keys: query, count, results."
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema violation, raised before the handler runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Calculation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/": {
      "get": {
        "operationId": "root",
        "summary": "Service banner: name, version and a short endpoint index.",
        "description": "A cheap identity check. Returns the API's own name and version string plus a hand-maintained index of its headline endpoints — useful for confirming which deployment you are talking to.\n\nRate limit: Inherits the app default (200 requests/minute per IP).",
        "tags": [
          "Lookup"
        ],
        "responses": {
          "200": {
            "description": "Success. Top-level keys: message, version, endpoints.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Top-level keys: message, version, endpoints."
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema violation, raised before the handler runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Calculation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/health": {
      "get": {
        "operationId": "health",
        "summary": "Liveness probe.",
        "description": "Returns a fixed healthy payload. Excluded from the service's request logging so that probing it every few seconds costs nothing in log volume.\n\nRate limit: Inherits the app default (200 requests/minute per IP).",
        "tags": [
          "Lookup"
        ],
        "responses": {
          "200": {
            "description": "Success. Top-level keys: status, service.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Top-level keys: status, service."
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema violation, raised before the handler runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Calculation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/debug/meminfo": {
      "get": {
        "operationId": "debugMeminfo",
        "summary": "Operational memory observability. Not a product endpoint.",
        "description": "Reports the answering worker's resident set size and peak RSS from /proc, the configured allocator, and the MCP horoscope cache occupancy. It exists to watch memory behaviour under load. With multiple workers each request may land on a different one, so a single sample means little. Read-only and cheap, but treat it as an internal detail that can change without notice.\n\nRate limit: Inherits the app default (200 requests/minute per IP).",
        "tags": [
          "Lookup"
        ],
        "responses": {
          "200": {
            "description": "Success. Top-level keys: allocator, rss_mb, rss_peak_mb, horoscope_cache.",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "Top-level keys: allocator, rss_mb, rss_peak_mb, horoscope_cache."
                }
              }
            }
          },
          "400": {
            "description": "Invalid input.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "422": {
            "description": "Schema violation, raised before the handler runs.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limited.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Calculation failed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "pdfSecret": {
        "type": "apiKey",
        "in": "header",
        "name": "X-PDF-Secret",
        "description": "Shared secret gating POST /horoscope/pdf. Reserved for the site's own backend; fails closed."
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "detail": {
            "type": "string",
            "description": "Human-readable reason, naming the field and the constraint it broke."
          }
        },
        "required": [
          "detail"
        ]
      },
      "BirthDetails": {
        "type": "object",
        "description": "The birth block shared by /horoscope, /horoscope/pdf, /gochara, /gochara/range, /muhurta and /saturn-timeline.",
        "properties": {
          "date": {
            "type": "string",
            "description": "Birth date, YYYY-MM-DD. Year must be 1900-2100, month 1-12, day 1-31."
          },
          "time": {
            "type": "string",
            "description": "Birth time, HH:MM:SS (24-hour). Hour 0-23, minute 0-59, second 0-59."
          },
          "place": {
            "type": "string",
            "description": "Birth place name. Cosmetic on the transit endpoints, where it defaults to an empty string; required on /horoscope."
          },
          "latitude": {
            "type": "number",
            "description": "Decimal degrees, -90 to 90."
          },
          "longitude": {
            "type": "number",
            "description": "Decimal degrees, -180 to 180."
          },
          "timezone": {
            "type": "number",
            "description": "UTC offset in hours, -12 to 14 (5.5 for IST). The service re-resolves the historical, DST-correct offset for the birth moment, so this is a hint rather than the last word."
          },
          "elevation": {
            "type": "number",
            "description": "Metres above sea level.",
            "default": 0
          },
          "ayanamsa_mode": {
            "type": "string",
            "description": "One of 18 honoured modes. Case-insensitive; uppercased server-side. SENTHIL, SUNDAR_SS and SIDM_USER are accepted by validation but NOT applied by the engine — SENTHIL and SUNDAR_SS silently return a LAHIRI chart while reporting their own ayanamsa_value. Do not use them.",
            "default": "LAHIRI",
            "enum": [
              "LAHIRI",
              "TRUE_CITRA",
              "TRUE_LAHIRI",
              "KP",
              "FAGAN",
              "RAMAN",
              "USHASHASHI",
              "YUKTESHWAR",
              "SURYASIDDHANTA",
              "SURYASIDDHANTA_MSUN",
              "ARYABHATA",
              "ARYABHATA_MSUN",
              "SS_CITRA",
              "TRUE_REVATI",
              "SS_REVATI",
              "TRUE_PUSHYA",
              "TRUE_MULA",
              "KP-SENTHIL"
            ]
          },
          "ayanamsa_value": {
            "type": "number",
            "description": "Custom ayanamsa in degrees, -30.0 to 30.0. Only meaningful with SIDM_USER."
          },
          "calculation_type": {
            "type": "string",
            "description": "\"drik\" (modern) or \"ss\" (Surya Siddhanta). Case-insensitive.",
            "default": "drik"
          },
          "node_type": {
            "type": "string",
            "description": "Rahu/Ketu node: \"mean\" or \"true\".",
            "default": "mean"
          },
          "bhava_madhya_method": {
            "type": "integer",
            "description": "House system, 1-5: 1=Equal-Lagna-Middle, 2=Equal-Lagna-Start, 3=Sripati, 4=KP, 5=Whole-Sign.",
            "default": 1
          },
          "language": {
            "type": "string",
            "description": "One of en, hi, ka, ta, te.",
            "default": "en"
          },
          "pravesha_type": {
            "type": "integer",
            "description": "0=Natal, 1=Annual, 2=Tithi-Pravesha.",
            "default": 0
          },
          "years": {
            "type": "integer",
            "description": "Year number for annual/progressed charts, 1-120.",
            "default": 1
          },
          "months": {
            "type": "integer",
            "description": "Month number for monthly charts, 1-12.",
            "default": 1
          },
          "sixty_hours": {
            "type": "integer",
            "description": "60-hour period for hora progressions. The validator enforces 1-1000.",
            "default": 1
          },
          "divisional_chart_factor": {
            "type": "integer",
            "description": "1-300. 1=D-1 (Rasi), 2=D-2 (Hora), 9=D-9 (Navamsa), and so on.",
            "default": 1
          },
          "chart_method": {
            "type": "integer",
            "description": "1-6. Which classical construction to use for the divisional chart.",
            "default": 1
          },
          "base_rasi": {
            "type": "integer",
            "description": "0-11 (0=Aries, 11=Pisces), for custom charts."
          },
          "count_from_end_of_sign": {
            "type": "boolean",
            "description": "Count divisions backward in even signs."
          },
          "varnada_method": {
            "type": "integer",
            "description": "Positive integer.",
            "default": 1
          },
          "tamil_month_method": {
            "type": "integer",
            "description": "0=Ravi Annasamy, 1=V4.3.5, 2=10AM Start, 3=Midday UTC.",
            "default": 3
          },
          "kali_start_year": {
            "type": "integer",
            "description": "1=Prabhava (South Indian) or 13=Pramaadhi (North Indian).",
            "default": 13
          }
        },
        "required": [
          "date",
          "time",
          "place",
          "latitude",
          "longitude",
          "timezone"
        ]
      }
    }
  }
}
