{
  "openapi": "3.0.3",
  "info": {
    "title": "CosmyDay Astrology",
    "version": "1.0.0",
    "description": "Free, public, read-only astrology data. Natal charts are computed from the Swiss Ephemeris — the same high-precision engine professional astrology software uses — rather than approximated. Horoscope and sky-event content is regenerated on a schedule.\n\nNo authentication, no API key, no account. Please keep request volume reasonable and cache responses where you can; there is no paid tier to upgrade to.\n\nCORS: /natal, /events/* and /content/* are open to any origin, so you can call them directly from a browser. /search-location is restricted (it proxies OpenStreetMap under a shared rate limit) - call that one server-side.",
    "contact": { "name": "CosmyDay", "email": "admin@cosmyday.com", "url": "https://cosmyday.com" },
    "license": { "name": "Free for any use, attribution appreciated" }
  },
  "servers": [{ "url": "https://api.cosmyday.com", "description": "Production" }],
  "tags": [
    { "name": "charts", "description": "Astronomical calculation" },
    { "name": "events", "description": "Retrogrades, lunations, eclipses, ingresses" },
    { "name": "content", "description": "Generated horoscope text" },
    { "name": "utility", "description": "Health and geocoding" }
  ],
  "paths": {
    "/health": {
      "get": {
        "tags": ["utility"],
        "summary": "Service health",
        "operationId": "health",
        "responses": {
          "200": {
            "description": "Service is up",
            "content": { "application/json": { "example": { "status": "ok", "service": "cosmyday-api" } } }
          }
        }
      }
    },
    "/natal": {
      "post": {
        "tags": ["charts"],
        "summary": "Calculate a natal chart",
        "description": "Returns planetary positions, house cusps and aspects for a birth moment and location. Time is interpreted as local time at the given coordinates; the timezone is resolved from the coordinates automatically. Houses use Placidus, falling back to Whole Sign above 66° latitude where Placidus is undefined.",
        "operationId": "natal",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/NatalRequest" },
              "example": { "year": 1990, "month": 6, "day": 15, "hour": 14, "minute": 30, "lat": 40.7128, "lon": -74.006 }
            }
          }
        },
        "responses": {
          "200": { "description": "Computed chart" },
          "422": { "description": "A field was out of range" }
        }
      }
    },
    "/events/upcoming": {
      "get": {
        "tags": ["events"],
        "summary": "Upcoming sky events",
        "description": "One unified timeline of retrograde stations, new and full moons, eclipses, zodiac season starts and planetary sign changes. Every date is computed from the Swiss Ephemeris and given in US Eastern Time.\n\nEach event carries an `importance` score from 1 to 100 reflecting how much attention it typically draws — filter with `min_importance` to get only the notable ones. An eclipse also appears as its underlying full or new moon, since it is both; the eclipse record scores higher.",
        "operationId": "upcomingEvents",
        "parameters": [
          { "name": "days", "in": "query", "schema": { "type": "integer", "default": 30, "minimum": 1, "maximum": 400 }, "description": "How far ahead to look" },
          { "name": "limit", "in": "query", "schema": { "type": "integer", "default": 50, "minimum": 1, "maximum": 200 } },
          { "name": "min_importance", "in": "query", "schema": { "type": "integer", "default": 0, "minimum": 0, "maximum": 100 }, "description": "60 and above is roughly 'notable'" },
          { "name": "kind", "in": "query", "schema": { "type": "string" }, "description": "Restrict to one kind; see /events/kinds" },
          { "name": "from_date", "in": "query", "schema": { "type": "string", "format": "date" }, "description": "Override today, as YYYY-MM-DD" }
        ],
        "responses": {
          "200": {
            "description": "Matching events, soonest first",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/EventList" },
                "example": {
                  "timezone": "America/New_York (Eastern Time)",
                  "computed_with": "Swiss Ephemeris",
                  "from": "2026-08-13",
                  "to": "2026-09-12",
                  "count": 2,
                  "events": [
                    {
                      "id": "eclipse-lunar-2026-08-28",
                      "date": "2026-08-28",
                      "kind": "eclipse_lunar",
                      "headline": "Partial Lunar Eclipse in Pisces",
                      "short": "A partial lunar eclipse at 5° Pisces, maximum 12:19 AM ET on August 28, 2026.",
                      "sign": "Pisces",
                      "time_et": "12:19 AM",
                      "importance": 78,
                      "url": "https://cosmyday.com/eclipse-calendar"
                    }
                  ]
                }
              }
            }
          },
          "400": { "description": "from_date was not YYYY-MM-DD" },
          "503": { "description": "Feed temporarily unavailable" }
        }
      }
    },
    "/events/kinds": {
      "get": {
        "tags": ["events"],
        "summary": "Event kinds with counts",
        "description": "The kinds present in the feed, so a client can build a filter without hardcoding a list that drifts.",
        "operationId": "eventKinds",
        "responses": { "200": { "description": "Kinds and counts" } }
      }
    },
    "/content/daily": {
      "get": { "tags": ["content"], "summary": "Daily horoscopes, all twelve signs", "operationId": "dailyAll", "responses": { "200": { "description": "All signs" } } }
    },
    "/content/daily/{sign}": {
      "get": {
        "tags": ["content"], "summary": "Daily horoscope for one sign", "operationId": "dailyOne",
        "parameters": [{ "$ref": "#/components/parameters/Sign" }],
        "responses": { "200": { "description": "One sign" }, "404": { "description": "Unknown sign" } }
      }
    },
    "/content/weekly": {
      "get": { "tags": ["content"], "summary": "Weekly horoscopes, all signs", "operationId": "weeklyAll", "responses": { "200": { "description": "All signs" } } }
    },
    "/content/weekly/{sign}": {
      "get": {
        "tags": ["content"], "summary": "Weekly horoscope for one sign", "operationId": "weeklyOne",
        "parameters": [{ "$ref": "#/components/parameters/Sign" }],
        "responses": { "200": { "description": "One sign" }, "404": { "description": "Unknown sign" } }
      }
    },
    "/content/monthly": {
      "get": { "tags": ["content"], "summary": "Monthly horoscopes, all signs", "operationId": "monthlyAll", "responses": { "200": { "description": "All signs" } } }
    },
    "/content/monthly/{sign}": {
      "get": {
        "tags": ["content"], "summary": "Monthly horoscope for one sign", "operationId": "monthlyOne",
        "parameters": [{ "$ref": "#/components/parameters/Sign" }],
        "responses": { "200": { "description": "One sign" }, "404": { "description": "Unknown sign" } }
      }
    },
    "/content/monthly-archive/list": {
      "get": { "tags": ["content"], "summary": "Every archived sign-month available", "operationId": "archiveList", "responses": { "200": { "description": "List of {sign, year, month}" } } }
    },
    "/content/monthly-archive/{sign}/{yyyy_mm}": {
      "get": {
        "tags": ["content"], "summary": "Archived monthly horoscope",
        "description": "A specific month's forecast for one sign, e.g. `/content/monthly-archive/leo/2026-03`.",
        "operationId": "archiveOne",
        "parameters": [
          { "$ref": "#/components/parameters/Sign" },
          { "name": "yyyy_mm", "in": "path", "required": true, "schema": { "type": "string", "pattern": "^\\d{4}-\\d{2}$" }, "example": "2026-03" }
        ],
        "responses": { "200": { "description": "That month" }, "404": { "description": "Not generated" } }
      }
    },
    "/content/moon": {
      "get": { "tags": ["content"], "summary": "Current moon phase article", "operationId": "moon", "responses": { "200": { "description": "Moon content" } } }
    },
    "/content/transit": {
      "get": { "tags": ["content"], "summary": "Today's transit article", "operationId": "transit", "responses": { "200": { "description": "Transit content" } } }
    },
    "/content/week-ahead": {
      "get": { "tags": ["content"], "summary": "Seven-day forecast", "operationId": "weekAhead", "responses": { "200": { "description": "Week ahead" } } }
    },
    "/content/monthly-overview": {
      "get": { "tags": ["content"], "summary": "Monthly overview article", "operationId": "monthlyOverview", "responses": { "200": { "description": "Overview" } } }
    },
    "/search-location": {
      "get": {
        "tags": ["utility"],
        "summary": "Find coordinates for a city",
        "description": "Geocoding for birth locations, proxied to OpenStreetMap Nominatim and rate limited to one request per second in line with their usage policy. Use it to turn a place name into the `lat`/`lon` that `/natal` needs.",
        "operationId": "searchLocation",
        "parameters": [{ "name": "q", "in": "query", "required": true, "schema": { "type": "string", "minLength": 2 }, "example": "New York" }],
        "responses": { "200": { "description": "Matching places" } }
      }
    }
  },
  "components": {
    "parameters": {
      "Sign": {
        "name": "sign", "in": "path", "required": true,
        "schema": {
          "type": "string",
          "enum": ["aries", "taurus", "gemini", "cancer", "leo", "virgo", "libra", "scorpio", "sagittarius", "capricorn", "aquarius", "pisces"]
        },
        "example": "leo"
      }
    },
    "schemas": {
      "NatalRequest": {
        "type": "object",
        "required": ["year", "month", "day", "hour", "minute", "lat", "lon"],
        "properties": {
          "year": { "type": "integer", "minimum": 1900, "maximum": 2100 },
          "month": { "type": "integer", "minimum": 1, "maximum": 12 },
          "day": { "type": "integer", "minimum": 1, "maximum": 31 },
          "hour": { "type": "integer", "minimum": 0, "maximum": 23, "description": "Local time at the given coordinates" },
          "minute": { "type": "integer", "minimum": 0, "maximum": 59 },
          "lat": { "type": "number", "minimum": -90, "maximum": 90 },
          "lon": { "type": "number", "minimum": -180, "maximum": 180 }
        }
      },
      "SkyEvent": {
        "type": "object",
        "properties": {
          "id": { "type": "string" },
          "date": { "type": "string", "format": "date" },
          "kind": { "type": "string", "example": "full_moon" },
          "headline": { "type": "string" },
          "short": { "type": "string", "description": "One sentence, suitable as a standalone summary" },
          "long": { "type": "string", "description": "A short explanatory paragraph" },
          "sign": { "type": "string", "nullable": true },
          "time_et": { "type": "string", "nullable": true },
          "importance": { "type": "integer", "minimum": 1, "maximum": 100 },
          "url": { "type": "string", "format": "uri" }
        }
      },
      "EventList": {
        "type": "object",
        "properties": {
          "timezone": { "type": "string" },
          "computed_with": { "type": "string" },
          "covers": { "type": "object" },
          "from": { "type": "string", "format": "date" },
          "to": { "type": "string", "format": "date" },
          "count": { "type": "integer" },
          "events": { "type": "array", "items": { "$ref": "#/components/schemas/SkyEvent" } }
        }
      }
    }
  }
}
