{
  "openapi": "3.1.0",
  "info": {
    "title": "OrbitAlert API",
    "description": "Satellite pass prediction and webhook alerting for ground-station operators, research labs, and satellite operations teams.\n\n**Core workflow**: sign in on the dashboard (mints your API key automatically) \u2192 `POST /alerts` with a webhook URL and ground-station coordinates \u2192 receive an HTTP POST a configurable number of minutes before each upcoming pass, with azimuth/elevation/duration and the current space-weather (Kp-index) context.\n\n**Authentication**: every endpoint below (except `meta` and account-bootstrap) requires an `X-API-Key: sk_live_<token>` header. Get your key from the dashboard's API Keys page, or via `POST /api-keys`.\n\nNew to the API? See the [getting started guide](/docs-guide) for copy-paste examples in curl, Python, and JavaScript.",
    "version": "1.0.0"
  },
  "paths": {
    "/health": {
      "get": {
        "tags": [
          "meta"
        ],
        "summary": "Health check",
        "description": "Checks every dependency instead of just returning 200 unconditionally.\n\nOnly a **database** failure returns overall status \"down\" (HTTP 503) \u2014\nthat's the one dependency this API genuinely cannot serve traffic\nwithout, and it's what Render's own healthCheckPath watches to decide\nwhether to keep routing to / restart this instance. Redis, the TLE\nsource, and a stale worker heartbeat degrade specific features (rate\nlimiting fails open, pass predictions may be stale, alerts may be\ndelayed) without meaning the API itself is broken, so they report\n\"degraded\" at HTTP 200 \u2014 visible here for humans/UptimeRobot, and\nsurfaced faster via the critical-alert check in \u03932.",
        "operationId": "health_health_get",
        "responses": {
          "200": {
            "description": "Per-dependency health: database, Redis, TLE source, worker",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "Response Health Health Get"
                }
              }
            }
          }
        }
      }
    },
    "/worker-status": {
      "get": {
        "tags": [
          "meta"
        ],
        "summary": "Webhook worker health",
        "description": "Return webhook worker health: last/next run times and today's delivery counts.\nThe worker writes a heartbeat file on every poll cycle; this endpoint reads it.",
        "operationId": "worker_status_worker_status_get",
        "responses": {
          "200": {
            "description": "Worker last-run time and today's alert delivery counts",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "Response Worker Status Worker Status Get"
                }
              }
            }
          }
        }
      }
    },
    "/status": {
      "get": {
        "tags": [
          "meta"
        ],
        "summary": "Public status page data",
        "description": "Public, unauthenticated status snapshot for the /status page: whether the\nAPI+database are reachable, webhook delivery reliability over the last 30\ndays, and when the delivery worker last ran. Degrades gracefully (never\nraises) so a database outage still renders a \"degraded\" status instead of\na 500.",
        "operationId": "public_status_status_get",
        "responses": {
          "200": {
            "description": "Overall operational status, webhook reliability, worker heartbeat",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "Response Public Status Status Get"
                }
              }
            }
          }
        }
      }
    },
    "/status/history": {
      "get": {
        "tags": [
          "meta"
        ],
        "summary": "Uptime history as raw JSON",
        "description": "Raw per-day uptime percentage for embedding in your own internal\ndashboards \u2014 the same self-tracked `uptime_checks` data GET /sla\nsummarizes, without the monthly rollup or incident list. One grouped\nquery regardless of `days` (func.date() groups natively on both Postgres\nand SQLite), not one query per day.",
        "operationId": "status_history_status_history_get",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 365,
              "minimum": 1,
              "description": "How many days of history to return",
              "default": 90,
              "title": "Days"
            },
            "description": "How many days of history to return"
          }
        ],
        "responses": {
          "200": {
            "description": "Daily uptime % for the requested window",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "Response Status History Status History Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/sla": {
      "get": {
        "tags": [
          "meta"
        ],
        "summary": "SLA uptime history",
        "description": "Public uptime/SLA proof for the Research/Enterprise 99.9% promise:\nrolling 30-day uptime %, uptime % per month for the last 6 months, and the\nincident list (root cause + postmortem once an operator adds one) for the\nlast 30 days. Backed by uptime_checks + incidents, both self-tracked every\n~60s by the health monitor \u2014 see UptimeCheck/Incident in models.py.",
        "operationId": "sla_status_sla_get",
        "responses": {
          "200": {
            "description": "Rolling 30-day uptime %, monthly history, and incident list",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "Response Sla Status Sla Get"
                }
              }
            }
          }
        }
      }
    },
    "/badge/uptime.svg": {
      "get": {
        "tags": [
          "meta"
        ],
        "summary": "Embeddable uptime badge (SVG)",
        "description": "Public, unauthenticated \u2014 designed to be hotlinked from a customer's\nREADME or docs (e.g.\n`![OrbitAlert Uptime](https://api.orbitalert.net/badge/uptime.svg)`), so\nthis can't require an API key. Uses the exact same _uptime_pct() rolling-\nwindow calculation GET /sla and GET /status/history already use over\nUptimeCheck rows \u2014 one uptime formula for the whole product, not a\nsecond one reinvented here. Cached briefly since it'll be hit often by\nimage-embedding clients that don't respect application-level caching.",
        "operationId": "uptime_badge_badge_uptime_svg_get",
        "parameters": [
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 365,
              "minimum": 1,
              "description": "Uptime window in days",
              "default": 30,
              "title": "Days"
            },
            "description": "Uptime window in days"
          }
        ],
        "responses": {
          "200": {
            "description": "shields.io-style SVG badge for embedding in a README or docs page",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/changelog.json": {
      "get": {
        "tags": [
          "meta"
        ],
        "summary": "Changelog entries as JSON",
        "description": "Public, unauthenticated. CHANGELOG (changelog_data.py) is the single\nsource of truth \u2014 frontend/app/changelog/page.tsx fetches this endpoint\nrather than keeping its own duplicate copy, and GET /changelog.xml below\nrenders the same list as RSS, so there's exactly one place to edit\nwhenever something new ships.",
        "operationId": "changelog_json_changelog_json_get",
        "responses": {
          "200": {
            "description": "Newest-first list of changelog entries",
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "type": "object"
                  },
                  "type": "array",
                  "title": "Response Changelog Json Changelog Json Get"
                }
              }
            }
          }
        }
      }
    },
    "/changelog.xml": {
      "get": {
        "tags": [
          "meta"
        ],
        "summary": "Changelog as an RSS 2.0 feed",
        "description": "Public, unauthenticated. Hand-built RSS 2.0 XML \u2014 simple enough not to need a library.",
        "operationId": "changelog_rss_changelog_xml_get",
        "responses": {
          "200": {
            "description": "RSS feed of the same entries as GET /changelog.json",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          }
        }
      }
    },
    "/testimonials": {
      "get": {
        "tags": [
          "meta"
        ],
        "summary": "Featured testimonials",
        "description": "Public \u2014 returns only testimonials marked featured=true, for the landing page.",
        "operationId": "list_testimonials_testimonials_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/TestimonialOut"
                  },
                  "type": "array",
                  "title": "Response List Testimonials Testimonials Get"
                }
              }
            }
          }
        }
      }
    },
    "/passes": {
      "get": {
        "tags": [
          "passes"
        ],
        "summary": "Predict satellite passes",
        "description": "Return predicted passes for a satellite over a ground station.\n\nThe satellite can be identified by:\n- Celestrak name: `NOAA-20`, `ISS (ZARYA)`, `METEOR-M 2-3`\n- NORAD catalog number: `43013`, `25544`\n\nPasses whose peak elevation falls below **min_elevation** are excluded.\n\nTracking more than one satellite or ground station? See **POST /passes/batch**\n\u2014 up to 20 queries in a single request, counted as one call against your\nrate limit.",
        "operationId": "get_passes_passes_get",
        "parameters": [
          {
            "name": "satellite",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100,
              "pattern": "^[A-Za-z0-9 ()\\-\\._/]+$",
              "description": "Celestrak satellite name (`NOAA-20`) or NORAD ID (`43013`)",
              "examples": {
                "name": {
                  "value": "NOAA-20"
                },
                "norad": {
                  "value": "43013"
                }
              },
              "title": "Satellite"
            },
            "description": "Celestrak satellite name (`NOAA-20`) or NORAD ID (`43013`)"
          },
          {
            "name": "lat",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "maximum": 90.0,
              "minimum": -90.0,
              "description": "Observer latitude (\u00b0)",
              "title": "Lat"
            },
            "description": "Observer latitude (\u00b0)"
          },
          {
            "name": "lon",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "maximum": 180.0,
              "minimum": -180.0,
              "description": "Observer longitude (\u00b0)",
              "title": "Lon"
            },
            "description": "Observer longitude (\u00b0)"
          },
          {
            "name": "alt_m",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": 0.0,
              "description": "Observer altitude above sea level (m)",
              "default": 0.0,
              "title": "Alt M"
            },
            "description": "Observer altitude above sea level (m)"
          },
          {
            "name": "hours",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "maximum": 72.0,
              "minimum": 1.0,
              "description": "Prediction window in hours (1\u201372)",
              "default": 24.0,
              "title": "Hours"
            },
            "description": "Prediction window in hours (1\u201372)"
          },
          {
            "name": "min_elevation",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "maximum": 90.0,
              "minimum": 0.0,
              "description": "Minimum peak elevation (\u00b0) to include",
              "default": 10.0,
              "title": "Min Elevation"
            },
            "description": "Minimum peak elevation (\u00b0) to include"
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PassEventOut"
                  },
                  "title": "Response Get Passes Passes Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/passes/batch": {
      "post": {
        "tags": [
          "passes"
        ],
        "summary": "Predict passes for up to 20 satellite/ground-station combinations in one call",
        "description": "Same prediction as **GET /passes**, batched \u2014 for a lab tracking a\nmulti-satellite constellation or a multi-site ground-station network in\none sweep. Counts as a single request against your plan's rate limit,\nregardless of how many queries the batch contains.\n\nEach query fails independently \u2014 one unknown satellite name or transient\nTLE-fetch error doesn't drop the rest of the batch; check the `error`\nfield on each result.",
        "operationId": "get_passes_batch_passes_batch_post",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchPassRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/BatchPassResult"
                  },
                  "title": "Response Get Passes Batch Passes Batch Post"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/satellites/search": {
      "get": {
        "tags": [
          "passes"
        ],
        "summary": "Search the satellite catalog (autocomplete)",
        "description": "Backs the satellite autocomplete in the alert-creation form. Matches\nagainst `satellite_catalog` (refreshed every 24h from Celestrak's\nactive-satellites list \u2014 see satellite_catalog.py) by case-insensitive\nname substring, or by exact NORAD ID when `q` is all digits.\n\nAn empty `q` returns a short list of well-known satellites (plus a\ncouple of live Starlink examples) instead of an arbitrary slice of the\n~8,000-row catalog.",
        "operationId": "search_satellites_satellites_search_get",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100,
              "description": "Name substring or exact NORAD ID",
              "default": "",
              "title": "Q"
            },
            "description": "Name substring or exact NORAD ID"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 50,
              "minimum": 1,
              "default": 20,
              "title": "Limit"
            }
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SatelliteSearchResult"
                  },
                  "title": "Response Search Satellites Satellites Search Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/satellites/search/public": {
      "get": {
        "tags": [
          "passes"
        ],
        "summary": "Search the satellite catalog (autocomplete, no account required)",
        "description": "Same lookup as GET /satellites/search, but usable by anonymous visitors \u2014\nlets the public homepage map's search work before sign-in. Same catalog\ndata is already public via GET /satellites/positions, so this exposes\nnothing new; it's only unauthenticated (hence per-IP throttled instead of\nper-account) rather than unmetered-but-still-identified.",
        "operationId": "search_satellites_public_satellites_search_public_get",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100,
              "description": "Name substring or exact NORAD ID",
              "default": "",
              "title": "Q"
            },
            "description": "Name substring or exact NORAD ID"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 50,
              "minimum": 1,
              "default": 20,
              "title": "Limit"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SatelliteSearchResult"
                  },
                  "title": "Response Search Satellites Public Satellites Search Public Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/satellites/positions": {
      "get": {
        "tags": [
          "passes"
        ],
        "summary": "Current ground-track position for a set of satellites (map display)",
        "description": "Public \u2014 no API key required, same as the orbital data itself (Celestrak\nis a public feed). Returns the current sub-satellite point (nadir ground\ntrack) for each requested satellite, for the live map on the homepage.\n\nReads cached TLEs from satellite_catalog (refreshed every 24h \u2014 see\nsatellite_catalog.py) rather than calling Celestrak per request. IDs with\nno matching catalog row, or whose TLE fails to propagate (decayed orbit),\nare silently omitted rather than erroring the whole request.",
        "operationId": "get_satellite_positions_satellites_positions_get",
        "parameters": [
          {
            "name": "ids",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "maxLength": 1000,
              "description": "Comma-separated NORAD IDs, up to 50",
              "examples": [
                "25544,43013,33591"
              ],
              "title": "Ids"
            },
            "description": "Comma-separated NORAD IDs, up to 50"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/SatellitePositionOut"
                  },
                  "title": "Response Get Satellite Positions Satellites Positions Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/satellites/positions/all": {
      "get": {
        "tags": [
          "passes"
        ],
        "summary": "Current ground-track positions for the entire catalog (map backdrop layer)",
        "description": "Public \u2014 powers the dim \"all satellites\" backdrop on the homepage map, as\nopposed to GET /satellites/positions which computes on-demand for a\ncaller-chosen set of individually tracked/favorited satellites.\n\nServed from a cache recomputed in the background roughly every\n60 seconds (see _run_all_positions_refresh) instead of propagating the\nwhole catalog per request, so cost doesn't scale with concurrent\nvisitors. Empty until the first background refresh completes after\nstartup. Cache-Control lets browsers/CDNs also dedupe repeat fetches\nwithin that window.",
        "operationId": "get_all_satellite_positions_satellites_positions_all_get",
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "items": {
                    "$ref": "#/components/schemas/SatellitePositionLite"
                  },
                  "type": "array",
                  "title": "Response Get All Satellite Positions Satellites Positions All Get"
                }
              }
            }
          }
        }
      }
    },
    "/satellites/{norad_id}/tle": {
      "get": {
        "tags": [
          "passes"
        ],
        "summary": "Raw TLE for a satellite (Pro plan; live lookup on Enterprise)",
        "description": "Raw two-line-element data for feeding into your own propagation/analysis\ntooling, rather than OrbitAlert's processed pass predictions.\n\nBy default reads the cached catalog entry (refreshed every 24h). With\n``fresh=true`` it instead does a live lookup \u2014 the same source used by\nGET /passes \u2014 for satellites whose orbit changed since the last catalog\nrefresh (e.g. after a maneuver); an Enterprise-only escape hatch since\nit's slower and hits an external provider on every call.",
        "operationId": "get_satellite_tle_satellites__norad_id__tle_get",
        "parameters": [
          {
            "name": "norad_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "title": "Norad Id"
            }
          },
          {
            "name": "fresh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "boolean",
              "description": "Bypass the 24h catalog cache with a live Celestrak/SatNOGS lookup (Enterprise only)",
              "default": false,
              "title": "Fresh"
            },
            "description": "Bypass the 24h catalog cache with a live Celestrak/SatNOGS lookup (Enterprise only)"
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SatelliteTleOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/coverage": {
      "get": {
        "tags": [
          "passes"
        ],
        "summary": "Which of your ground stations currently see which of your tracked satellites (Enterprise)",
        "description": "Cross-references your organization's ground stations against the\nsatellites on your active alerts, and returns every (station,\nsatellite) pair that has the satellite above the horizon right now.\nBuilt on the same live-position machinery as the homepage map and the\nsame cached TLEs \u2014 nothing new to fetch, just a different cross-section\nof data already being computed, which is why this can be an Enterprise\nperk at no extra infrastructure cost.",
        "operationId": "get_coverage_coverage_get",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/CoverageEntry"
                  },
                  "title": "Response Get Coverage Coverage Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/conjunctions": {
      "get": {
        "tags": [
          "orbital-intelligence"
        ],
        "summary": "Predicted close approaches for your tracked satellites (Research/Enterprise)",
        "description": "Predicted close approaches (\"conjunctions\") between your organization's\ntracked satellites (from active alerts) and any other object in the\nsatellite catalog, refreshed in the background every 6 hours.\n\nScreening uses an altitude-band pre-filter followed by coarse-to-fine\nSGP4 propagation \u2014 see conjunctions.py. **miss_distance_km is a real\ngeometric result, but probability is a simplified heuristic** (Celestrak\nTLEs carry no covariance data); treat it as a relative severity signal,\nnot an authoritative Space-Track conjunction data message.",
        "operationId": "list_conjunctions_conjunctions_get",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ConjunctionEventOut"
                  },
                  "title": "Response List Conjunctions Conjunctions Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/link-budget": {
      "post": {
        "tags": [
          "orbital-intelligence"
        ],
        "summary": "RF link budget calculator (free-space path loss + atmospheric attenuation)",
        "description": "Stateless RF link budget: EIRP, free-space path loss, a weather/\nelevation-based atmospheric attenuation estimate, and received power.\n\nTwo ways to give it geometry:\n  - **Manual** \u2014 pass `slant_range_km` + `elevation_deg` directly (e.g.\n    from a GET /passes result, or a hypothetical scenario not tied to a\n    real pass).\n  - **Automatic** \u2014 pass `satellite` + `lat` + `lon` instead, and the\n    range/elevation are computed for you at the moment of peak elevation\n    (TCA) of the next qualifying pass \u2014 the representative best-case\n    link opportunity for that satellite/station pair.\n\nPass **rx_sensitivity_dbm** to also get `link_margin_db` and a boolean\n`link_closes`. The response always echoes back the actual\n`slant_range_km`/`elevation_deg` used, plus `computed_from` so you can\ntell which mode produced them.",
        "operationId": "post_link_budget_link_budget_post",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LinkBudgetRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LinkBudgetOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/optimize-location": {
      "post": {
        "tags": [
          "orbital-intelligence"
        ],
        "summary": "Rank candidate ground-station locations by predicted satellite visibility",
        "description": "Given 2-20 candidate ground-station locations and either a single\n**norad_id** or a **constellation** name substring, ranks the candidates\nby predicted visibility of the target satellite(s) \u2014 passes/day, average\npeak elevation, and total visible minutes over **duration_hours**. The\ntop-ranked candidate is flagged `\"recommended\": true`.",
        "operationId": "post_optimize_location_optimize_location_post",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OptimizeLocationRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/OptimizeLocationResult"
                  },
                  "title": "Response Post Optimize Location Optimize Location Post"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/constellation/{name}/coverage": {
      "get": {
        "tags": [
          "orbital-intelligence"
        ],
        "summary": "Aggregate hourly visibility coverage for every satellite matching a constellation name",
        "description": "Matches **name** as a case-insensitive substring against the satellite\ncatalog (e.g. `starlink`, `oneweb`, `noaa`), capped at 200 matches, then\naggregates every matched satellite's predicted passes into hourly\ncoverage buckets: what fraction of each hour has at least one satellite\nabove **min_elevation**, and the peak number simultaneously visible.\n\n404 if the name matches nothing in the catalog.",
        "operationId": "get_constellation_coverage_constellation__name__coverage_get",
        "parameters": [
          {
            "name": "name",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Name"
            }
          },
          {
            "name": "lat",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "maximum": 90.0,
              "minimum": -90.0,
              "description": "Observer latitude (\u00b0)",
              "title": "Lat"
            },
            "description": "Observer latitude (\u00b0)"
          },
          {
            "name": "lon",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "maximum": 180.0,
              "minimum": -180.0,
              "description": "Observer longitude (\u00b0)",
              "title": "Lon"
            },
            "description": "Observer longitude (\u00b0)"
          },
          {
            "name": "alt_m",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": 0.0,
              "description": "Observer altitude above sea level (m)",
              "default": 0.0,
              "title": "Alt M"
            },
            "description": "Observer altitude above sea level (m)"
          },
          {
            "name": "hours",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "maximum": 72.0,
              "minimum": 1.0,
              "description": "Prediction window in hours (1-72)",
              "default": 24.0,
              "title": "Hours"
            },
            "description": "Prediction window in hours (1-72)"
          },
          {
            "name": "min_elevation",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "maximum": 90.0,
              "minimum": 0.0,
              "description": "Minimum peak elevation (\u00b0) to count as visible",
              "default": 10.0,
              "title": "Min Elevation"
            },
            "description": "Minimum peak elevation (\u00b0) to count as visible"
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConstellationCoverageOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/passes/doppler": {
      "get": {
        "tags": [
          "orbital-intelligence"
        ],
        "summary": "Doppler shift curve across the next qualifying pass",
        "description": "Finds the next pass of **satellite** over the given ground station within\n**hours**, then walks that pass's AOS\u2192LOS window at **step_seconds**\nresolution computing slant range, range-rate, and the resulting Doppler\nshift for **frequency_mhz** (positive = approaching / blue-shifted).\n\n404 if no qualifying pass is found in the search window.",
        "operationId": "get_pass_doppler_passes_doppler_get",
        "parameters": [
          {
            "name": "satellite",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "minLength": 1,
              "maxLength": 100,
              "pattern": "^[A-Za-z0-9 ()\\-\\._/]+$",
              "description": "Celestrak satellite name (`NOAA-20`) or NORAD ID (`43013`)",
              "title": "Satellite"
            },
            "description": "Celestrak satellite name (`NOAA-20`) or NORAD ID (`43013`)"
          },
          {
            "name": "lat",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "maximum": 90.0,
              "minimum": -90.0,
              "description": "Observer latitude (\u00b0)",
              "title": "Lat"
            },
            "description": "Observer latitude (\u00b0)"
          },
          {
            "name": "lon",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "maximum": 180.0,
              "minimum": -180.0,
              "description": "Observer longitude (\u00b0)",
              "title": "Lon"
            },
            "description": "Observer longitude (\u00b0)"
          },
          {
            "name": "alt_m",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": 0.0,
              "description": "Observer altitude above sea level (m)",
              "default": 0.0,
              "title": "Alt M"
            },
            "description": "Observer altitude above sea level (m)"
          },
          {
            "name": "frequency_mhz",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "maximum": 100000.0,
              "exclusiveMinimum": 0.0,
              "description": "Carrier frequency (MHz)",
              "title": "Frequency Mhz"
            },
            "description": "Carrier frequency (MHz)"
          },
          {
            "name": "step_seconds",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 60,
              "minimum": 1,
              "description": "Time step across the pass (s)",
              "default": 5,
              "title": "Step Seconds"
            },
            "description": "Time step across the pass (s)"
          },
          {
            "name": "hours",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "maximum": 72.0,
              "minimum": 1.0,
              "description": "Search window for the next qualifying pass",
              "default": 24.0,
              "title": "Hours"
            },
            "description": "Search window for the next qualifying pass"
          },
          {
            "name": "min_elevation",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "maximum": 90.0,
              "minimum": 0.0,
              "description": "Minimum peak elevation (\u00b0) to qualify",
              "default": 10.0,
              "title": "Min Elevation"
            },
            "description": "Minimum peak elevation (\u00b0) to qualify"
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DopplerCurveOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/space-weather": {
      "get": {
        "tags": [
          "space-weather"
        ],
        "summary": "Current NOAA Kp index",
        "description": "Return the current planetary Kp index from NOAA SWPC.\n\nData is fetched from NOAA and cached for 5 minutes.\n\n**Severity scale**\n| Level      | Kp    | NOAA classification |\n|------------|-------|---------------------|\n| quiet      | 0\u20133   | Normal              |\n| unsettled  | 4     | Borderline          |\n| storm      | 5\u20136   | G1\u2013G2               |\n| severe     | \u2265 7   | G3+                 |",
        "operationId": "get_space_weather_space_weather_get",
        "responses": {
          "200": {
            "description": "Kp index, storm severity, and last-updated timestamp",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "Response Get Space Weather Space Weather Get"
                }
              }
            }
          }
        }
      }
    },
    "/users/sync": {
      "post": {
        "tags": [
          "users"
        ],
        "summary": "Clerk post-login user sync",
        "description": "Called by the frontend immediately after Clerk sign-in.\nFinds or creates the user in our database by email.\nIf the user has no active API keys, generates the first one and returns it\n(the key is only ever shown once).\n\nSecurity: when Clerk is configured the account is keyed off the **verified**\nemail inside the Bearer session token \u2014 the body email is ignored, so a\ncaller can never sync (and mint a key for) an account that isn't theirs.\nWhen Clerk is not yet wired up, the endpoint is strictly rate-limited.",
        "operationId": "sync_user_users_sync_post",
        "parameters": [
          {
            "name": "authorization",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Authorization"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "title": "Payload"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "Response Sync User Users Sync Post"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/users": {
      "post": {
        "tags": [
          "users"
        ],
        "summary": "Create user account",
        "description": "Create a new user account.\n\nReturns the `id` field \u2014 pass it as the **X-User-Id** header when calling\n**POST /api-keys** to generate your first API key.",
        "operationId": "create_user_users_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UserCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api-keys": {
      "post": {
        "tags": [
          "api-keys"
        ],
        "summary": "Generate API key",
        "description": "Generate a new API key for a user.\n\nThe full key (``sk_live_...``) is returned **once** and never stored.\nCopy it immediately \u2014 subsequent calls show only the `key_prefix`.\n\nSecurity: when Clerk is configured the caller must present a Bearer session\ntoken whose verified email matches the target account \u2014 preventing anyone\nfrom minting keys for another user's ``X-User-Id``. When Clerk is off the\nendpoint is strictly rate-limited per IP.",
        "operationId": "create_api_key_api_keys_post",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200,
              "default": "Default",
              "title": "Name"
            }
          },
          {
            "name": "X-User-Id",
            "in": "header",
            "required": true,
            "schema": {
              "type": "string",
              "description": "User ID from **POST /users** (required to bootstrap the first key)",
              "title": "X-User-Id"
            },
            "description": "User ID from **POST /users** (required to bootstrap the first key)"
          },
          {
            "name": "authorization",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "Authorization"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiKeyCreateOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "api-keys"
        ],
        "summary": "List API keys",
        "description": "Return all active API keys for the authenticated user (prefix only \u2014 never full key).",
        "operationId": "list_api_keys_api_keys_get",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ApiKeyOut"
                  },
                  "title": "Response List Api Keys Api Keys Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/api-keys/{key_id}": {
      "delete": {
        "tags": [
          "api-keys"
        ],
        "summary": "Revoke API key",
        "description": "Soft-delete (revoke) an API key.  The key cannot be reinstated.",
        "operationId": "revoke_api_key_api_keys__key_id__delete",
        "parameters": [
          {
            "name": "key_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Key Id"
            }
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/organizations/me": {
      "get": {
        "tags": [
          "organizations"
        ],
        "summary": "Current organization",
        "description": "Return the caller's organization (plan, name, creation date).",
        "operationId": "get_current_organization_organizations_me_get",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrganizationOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/organizations/members": {
      "get": {
        "tags": [
          "organizations"
        ],
        "summary": "List organization members",
        "description": "Return every member of the caller's organization and their role. Any role may view.",
        "operationId": "list_organization_members_organizations_members_get",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/OrganizationMemberOut"
                  },
                  "title": "Response List Organization Members Organizations Members Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/organizations/members/{member_id}": {
      "patch": {
        "tags": [
          "organizations"
        ],
        "summary": "Change a member's role",
        "description": "Requires the admin role. Cannot demote the organization's last admin.",
        "operationId": "update_member_role_organizations_members__member_id__patch",
        "parameters": [
          {
            "name": "member_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Member Id"
            }
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MemberRoleUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrganizationMemberOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "organizations"
        ],
        "summary": "Remove a member",
        "description": "Requires the admin role. Cannot remove the organization's last admin.",
        "operationId": "remove_member_organizations_members__member_id__delete",
        "parameters": [
          {
            "name": "member_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Member Id"
            }
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/organizations/invites": {
      "post": {
        "tags": [
          "organizations"
        ],
        "summary": "Invite a teammate",
        "description": "Invite an email address to join the caller's organization with a given\nrole. They join automatically the first time they sign in with that\nemail. Requires the admin role.",
        "operationId": "create_invite_organizations_invites_post",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrganizationInviteCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OrganizationInviteOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/audit-logs": {
      "get": {
        "tags": [
          "organizations"
        ],
        "summary": "Organization audit trail",
        "description": "Return the caller's organization's audit trail, most recent first. Requires the admin role.",
        "operationId": "list_audit_logs_audit_logs_get",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 500,
              "minimum": 1,
              "default": 50,
              "title": "Limit"
            }
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AuditLogOut"
                  },
                  "title": "Response List Audit Logs Audit Logs Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/organizations/allowed-ips": {
      "get": {
        "tags": [
          "organizations"
        ],
        "summary": "List the organization's IP allowlist",
        "operationId": "list_allowed_ips_organizations_allowed_ips_get",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AllowedIpOut"
                  },
                  "title": "Response List Allowed Ips Organizations Allowed Ips Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "organizations"
        ],
        "summary": "Add an IP (or CIDR range) to the allowlist",
        "description": "Research/Enterprise only. Once an organization has at least one row here,\nevery API request must originate from an allowlisted IP/CIDR \u2014 see\n_check_ip_allowlist(). Free, Starter (plan key ``hobbyist``), and Pro\nplans get 403 on this endpoint.",
        "operationId": "create_allowed_ip_organizations_allowed_ips_post",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AllowedIpCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AllowedIpOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/organizations/allowed-ips/{allowed_ip_id}": {
      "delete": {
        "tags": [
          "organizations"
        ],
        "summary": "Remove an IP from the allowlist",
        "operationId": "delete_allowed_ip_organizations_allowed_ips__allowed_ip_id__delete",
        "parameters": [
          {
            "name": "allowed_ip_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Allowed Ip Id"
            }
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/organizations/custom-domain": {
      "post": {
        "tags": [
          "organizations"
        ],
        "summary": "Register a custom domain for white-label branding (Enterprise only)",
        "description": "Starts domain-ownership verification. Returns a CNAME target to add as a\nDNS record; the domain is not considered verified until POST\n.../custom-domain/verify confirms that record resolves.",
        "operationId": "request_custom_domain_organizations_custom_domain_post",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CustomDomainRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomDomainOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "organizations"
        ],
        "summary": "Remove the organization's custom domain",
        "operationId": "delete_custom_domain_organizations_custom_domain_delete",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/organizations/custom-domain/verify": {
      "post": {
        "tags": [
          "organizations"
        ],
        "summary": "Verify the pending custom domain's CNAME record",
        "operationId": "verify_custom_domain_organizations_custom_domain_verify_post",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CustomDomainOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/organizations/rate-limit-request": {
      "post": {
        "tags": [
          "organizations"
        ],
        "summary": "Request a rate-limit increase for your organization (Enterprise only)",
        "description": "Emails OPS_ALERT_EMAIL with the request \u2014 rate limits are raised manually\nper-organization (a Redis key override), not through a self-service\ncontrol, since each request needs a human judgment call about legitimate\nburst need vs. abuse.",
        "operationId": "request_rate_limit_increase_organizations_rate_limit_request_post",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RateLimitIncreaseRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "Response Request Rate Limit Increase Organizations Rate Limit Request Post"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/organizations/enterprise-contact": {
      "post": {
        "tags": [
          "organizations"
        ],
        "summary": "Request to talk to sales about the Enterprise plan",
        "description": "Enterprise (\u20ac1,499/mo) is deliberately not self-service checkout \u2014 an\ninstitutional buyer expects a conversation, not a credit-card form, and\nsome want invoice/PO billing instead of a card on file. Emails\nOPS_ALERT_EMAIL with the inquiry; available to any signed-in admin\nregardless of current plan, since this is how you'd get *onto* Enterprise.",
        "operationId": "request_enterprise_contact_organizations_enterprise_contact_post",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/EnterpriseContactRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "Response Request Enterprise Contact Organizations Enterprise Contact Post"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/enterprise-contact/public": {
      "post": {
        "tags": [
          "organizations"
        ],
        "summary": "Request to talk to sales about the Enterprise plan (no account required)",
        "description": "Anonymous variant of POST /organizations/enterprise-contact. The landing\npage's Enterprise \"Talk to sales\" CTA targets prospects who by definition\nhave no account yet \u2014 requiring sign-up before a sales conversation is a\ndead end for exactly the buyers Enterprise exists for.\n\nNo database writes (there is no organization to attach anything to);\nthrottled per-IP like the other unauthenticated bootstrap endpoints so it\ncan't be used as a spam cannon toward OPS_ALERT_EMAIL.",
        "operationId": "request_enterprise_contact_public_enterprise_contact_public_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PublicEnterpriseContactRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "Response Request Enterprise Contact Public Enterprise Contact Public Post"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/contact": {
      "post": {
        "tags": [
          "organizations"
        ],
        "summary": "General contact / support form (no account required)",
        "description": "Catch-all \"Contact / Support\" form for anyone with a question that isn't\nspecifically an Enterprise sales inquiry \u2014 a bug report, an integration\nquestion, anything. Anonymous by design, same reasoning as\nPOST /enterprise-contact/public: asking a question shouldn't require\nsigning up first. Mirrors that endpoint's shape (per-IP throttled,\nHTML-escaped, emails OPS_ALERT_EMAIL, log-only fallback if unset).",
        "operationId": "submit_general_contact_contact_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GeneralContactRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "Response Submit General Contact Contact Post"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/account/export-data": {
      "get": {
        "tags": [
          "organizations"
        ],
        "summary": "Export all of your organization's data (GDPR data portability)",
        "description": "Returns every record the organization owns \u2014 profile, members, alerts,\nground stations, webhook delivery logs, audit trail, and IP allowlist \u2014\nas a single downloadable JSON file. Available to any org member (any\nrole may read their own organization's data).",
        "operationId": "export_account_data_account_export_data_get",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/account/dpa-pdf": {
      "get": {
        "tags": [
          "organizations"
        ],
        "summary": "Download a personalized, signable Data Processing Agreement (PDF)",
        "description": "Renders the full Data Processing Agreement (see dpa_pdf.py) \u2014 title page,\ntable of contents, all 15 numbered sections, and the three annexes \u2014\nauto-filled with the caller's organization name and today's date, for a\nprocurement/legal team to file or countersign. Carries the same legal\ntext as the online copy at /dpa.",
        "operationId": "download_dpa_pdf_account_dpa_pdf_get",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/account/delete-request": {
      "post": {
        "tags": [
          "organizations"
        ],
        "summary": "Request permanent deletion of your organization (GDPR erasure)",
        "description": "Emails the requesting admin a confirmation link. Nothing is deleted until\nthat link is used (DELETE /account) \u2014 this step only proves the requester\ncontrols the org's admin account, matching the \"confirmation \u03bc\u03ad\u03c3\u03c9 email\"\nrequirement for such an irreversible action.",
        "operationId": "request_account_deletion_account_delete_request_post",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "Response Request Account Deletion Account Delete Request Post"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/account": {
      "delete": {
        "tags": [
          "organizations"
        ],
        "summary": "Confirm permanent deletion of your organization (GDPR erasure)",
        "description": "Consumes a token minted by POST /account/delete-request. Permanently\nerases every record scoped to the organization \u2014 deleted explicitly in\ndependency order here rather than relying on DB-level ON DELETE CASCADE,\nsince MOCK_MODE tests run on SQLite with foreign-key enforcement off.",
        "operationId": "delete_account_account_delete",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "description": "The token from the confirmation email",
              "title": "Token"
            },
            "description": "The token from the confirmation email"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "Response Delete Account Account Delete"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/alerts": {
      "post": {
        "tags": [
          "alerts"
        ],
        "summary": "Create webhook alert",
        "description": "Register a webhook URL to receive an HTTP POST before each upcoming pass.\n\n- `webhook_url` **must** use HTTPS\n- `minutes_before` sets how far ahead of AOS the webhook fires (1\u201360 min)\n- `min_elevation` filters out low passes (default 10\u00b0)\n\nPass notifications for the next 24 hours are queued immediately in Redis\nafter the alert is created. Requires the **admin** or **engineer** role.",
        "operationId": "create_alert_alerts_post",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AlertConfigCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlertConfigOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "get": {
        "tags": [
          "alerts"
        ],
        "summary": "List webhook alerts",
        "description": "Return all webhook alert configs (active and inactive) shared by the caller's organization.",
        "operationId": "list_alerts_alerts_get",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/AlertConfigOut"
                  },
                  "title": "Response List Alerts Alerts Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/alerts/{alert_id}": {
      "patch": {
        "tags": [
          "alerts"
        ],
        "summary": "Toggle alert active state",
        "description": "Toggle an alert config between active and inactive. Requires admin or engineer role.",
        "operationId": "toggle_alert_alerts__alert_id__patch",
        "parameters": [
          {
            "name": "alert_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Alert Id"
            }
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AlertConfigOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "alerts"
        ],
        "summary": "Delete webhook alert",
        "description": "Soft-delete an alert config (sets ``active=False``). Requires admin or engineer role.\n\nThe worker will no longer fire webhooks for deleted alerts.\nIn-flight Redis jobs for this alert may still deliver until they expire.",
        "operationId": "delete_alert_alerts__alert_id__delete",
        "parameters": [
          {
            "name": "alert_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Alert Id"
            }
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/alerts/{alert_id}/test-webhook": {
      "post": {
        "tags": [
          "alerts"
        ],
        "summary": "Send a single synchronous test webhook delivery",
        "description": "Fires **one** synchronous test delivery to the alert's `webhook_url` using\na synthetic, clearly-marked pass-notification payload (`\"test\": true`).\n\nRequires admin/engineer role (same as create/edit/delete on alerts) \u2014\nthis triggers a real outbound HTTP request as a side effect, so a\nread-only viewer key must not be able to invoke it repeatedly against\nwhatever URL the alert happens to be configured with.\n\nSigned with the same HMAC-SHA256 scheme as real deliveries (see\n`X-OrbitAlert-Signature` in `worker/webhook_worker.py`) using the alert's\nreal `webhook_secret`, so you can verify your signature-checking code\nagainst the real thing before an actual pass fires.\n\nThis is an interactive, single-attempt call (5 s timeout, no retries) \u2014\nit does **not** go through the worker's 3-attempt retry/backoff pipeline,\nand the result is intentionally **not** written to the webhook delivery\nhistory (`GET /webhook-logs`), since it isn't a real notification.",
        "operationId": "test_webhook_alerts__alert_id__test_webhook_post",
        "parameters": [
          {
            "name": "alert_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Alert Id"
            }
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TestWebhookOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/ground-stations": {
      "get": {
        "tags": [
          "ground-stations"
        ],
        "summary": "List saved ground stations",
        "description": "Return the caller's organization's saved observer locations.",
        "operationId": "list_ground_stations_ground_stations_get",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/GroundStationOut"
                  },
                  "title": "Response List Ground Stations Ground Stations Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "ground-stations"
        ],
        "summary": "Save a ground station",
        "description": "Save a named observer location for reuse when creating alert configs.\nCount is plan-limited: Starter 1, Pro 10, Research 25, Enterprise 50.\nRequires the admin or engineer role.",
        "operationId": "create_ground_station_ground_stations_post",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/GroundStationCreate"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GroundStationOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/ground-stations/{station_id}": {
      "delete": {
        "tags": [
          "ground-stations"
        ],
        "summary": "Delete a ground station",
        "description": "Requires the admin or engineer role.",
        "operationId": "delete_ground_station_ground_stations__station_id__delete",
        "parameters": [
          {
            "name": "station_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string",
              "title": "Station Id"
            }
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "204": {
            "description": "Successful Response"
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/affiliates/apply": {
      "post": {
        "tags": [
          "affiliates"
        ],
        "summary": "Apply to become an affiliate partner",
        "description": "Public application form (W6 /affiliates/join). Starts as status=pending.",
        "operationId": "apply_affiliate_affiliates_apply_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AffiliateApplyCreate"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AffiliateOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/affiliates/magic-link": {
      "post": {
        "tags": [
          "affiliates"
        ],
        "summary": "Request an affiliate-portal login link",
        "description": "Emails a login link if the address matches an approved affiliate. Always\nreturns the same generic response so the endpoint can't be used to\nenumerate which emails have (or don't have) an affiliate account.",
        "operationId": "request_magic_link_affiliates_magic_link_post",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MagicLinkRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "Response Request Magic Link Affiliates Magic Link Post"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/affiliates/me": {
      "get": {
        "tags": [
          "affiliates"
        ],
        "summary": "Affiliate's own profile and stats",
        "operationId": "get_affiliate_stats_affiliates_me_get",
        "parameters": [
          {
            "name": "X-Affiliate-Token",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "title": "X-Affiliate-Token"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AffiliateStatsOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/webhook-logs": {
      "get": {
        "tags": [
          "alerts"
        ],
        "summary": "Recent webhook delivery logs",
        "description": "Return the most recent webhook delivery attempts for the authenticated user.\nStarter (and free) accounts only see the last 7 days; Pro/Founding/Enterprise\nsee full history. See GET /webhook-logs/export for a full CSV download.",
        "operationId": "list_webhook_logs_webhook_logs_get",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 100,
              "minimum": 1,
              "default": 10,
              "title": "Limit"
            }
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  },
                  "title": "Response List Webhook Logs Webhook Logs Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/webhook-logs/export": {
      "get": {
        "tags": [
          "alerts"
        ],
        "summary": "Export full webhook delivery history as CSV (Pro plan feature)",
        "description": "Download the caller's organization's complete webhook delivery history as CSV.",
        "operationId": "export_webhook_logs_webhook_logs_export_get",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/analytics/summary": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Customer-facing usage analytics",
        "description": "Aggregate usage analytics for the caller's organization \u2014 how many pass\nnotifications fired, when, and how reliably they were delivered. No plan\ngate: this is a base-plan value-add so customers can show their own\nstakeholders the platform's value, not a paid feature.\n\nThere's no persisted `passes` table (predictions are computed on\ndemand, never stored), so `passes_per_week` and `busiest_hours_utc`\ncount first-attempt `webhook_logs` rows \u2014 one per real pass notification,\nretries excluded \u2014 and `delivery_rate_by_month` counts every attempt.\nSee `avg_elevation_by_satellite.method`/`.reason` for why that one metric\nis a live, forward-looking estimate rather than historical data (peak\nelevation is never written to webhook_logs, so there's nothing historical\nto average).",
        "operationId": "get_analytics_summary_analytics_summary_get",
        "parameters": [
          {
            "name": "weeks",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 52,
              "minimum": 1,
              "description": "Lookback window in weeks",
              "default": 8,
              "title": "Weeks"
            },
            "description": "Lookback window in weeks"
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyticsSummaryOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/reports/monthly.pdf": {
      "get": {
        "tags": [
          "analytics"
        ],
        "summary": "Download a monthly usage PDF report (Research/Enterprise)",
        "description": "Renders a one-or-two-page PDF (see analytics_pdf.py): org name, report\nmonth, summary stats, top 5 tracked satellites by pass count, and any\nconjunction/webhook-health alerts on record for that org+month. Gated to\nResearch/Enterprise plans, matching the original spec. Also sent\nautomatically on the 1st of each month for the previous month \u2014 see\n_run_monthly_report_send.",
        "operationId": "download_monthly_report_reports_monthly_pdf_get",
        "parameters": [
          {
            "name": "month",
            "in": "query",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string",
                  "pattern": "^\\d{4}-(0[1-9]|1[0-2])$"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Report month as YYYY-MM. Defaults to the previous full calendar month.",
              "examples": [
                "2026-06"
              ],
              "title": "Month"
            },
            "description": "Report month as YYYY-MM. Defaults to the previous full calendar month."
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/activity-feed": {
      "get": {
        "tags": [
          "organizations"
        ],
        "summary": "Friendly team activity feed (built on the audit log)",
        "description": "A friendlier, visual presentation layer over the existing audit trail\n(see GET /audit-logs) \u2014 same `audit_logs` rows, no new data source, just\njoined to the acting user's email and mapped to a plain-English summary\nsentence. Most useful for organizations with more than one member (see\norganization_members/RBAC), but works for solo orgs too. Unlike\nGET /audit-logs (admin-only), any org member can view this.",
        "operationId": "get_activity_feed_activity_feed_get",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 200,
              "minimum": 1,
              "default": 50,
              "title": "Limit"
            }
          },
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ActivityFeedEntryOut"
                  },
                  "title": "Response Get Activity Feed Activity Feed Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/webhook-health": {
      "get": {
        "tags": [
          "alerts"
        ],
        "summary": "Predictive webhook delivery health alerts",
        "description": "Predictive failure detection (item 6): flags a specific alert's webhook\nendpoint once its delivery failure rate has been *worsening* for 72\nhours straight \u2014 three consecutive 24h buckets with a monotonically\nincreasing failure rate, and the most recent bucket over 25% \u2014 so you\ncan fix it before deliveries stop entirely. Computed hourly by\n_run_webhook_health_check; each row also triggers a best-effort email to\nyour organization's admin(s) at the time it was raised (subject to a\n24h cooldown per alert config \u2014 see WebhookHealthAlert in models.py).\n\nNo plan gate \u2014 this is basic reliability information, available on\nevery plan.",
        "operationId": "list_webhook_health_alerts_webhook_health_get",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/WebhookHealthAlertOut"
                  },
                  "title": "Response List Webhook Health Alerts Webhook Health Get"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/billing/create-checkout": {
      "post": {
        "tags": [
          "billing"
        ],
        "summary": "Create Stripe Checkout session",
        "description": "Create a Stripe Checkout session for the requested plan (hobbyist/pro/\nresearch/enterprise) and billing interval (monthly, or yearly \u2014 10x the\nmonthly price, i.e. 2 months free). The resulting subscription belongs\nto the caller's **organization**, not just them \u2014 every member benefits\nonce payment completes. Requires the admin role.\nReturns ``{checkout_url}`` \u2014 redirect the browser there to complete payment.",
        "operationId": "create_checkout_billing_create_checkout_post",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CheckoutRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "Response Create Checkout Billing Create Checkout Post"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/billing/status": {
      "get": {
        "tags": [
          "billing"
        ],
        "summary": "Current billing status",
        "description": "Return the caller's organization's current plan, Stripe customer ID, and next billing date.",
        "operationId": "billing_status_billing_status_get",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BillingStatusOut"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    },
    "/billing/portal": {
      "post": {
        "tags": [
          "billing"
        ],
        "summary": "Create Stripe customer portal session",
        "description": "Create a Stripe Billing Portal session so an org admin can manage invoices,\nchange payment method, or cancel.  Returns ``{portal_url}``.",
        "operationId": "billing_portal_billing_portal_post",
        "parameters": [
          {
            "name": "X-API-Key",
            "in": "header",
            "required": false,
            "schema": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "null"
                }
              ],
              "description": "Your `sk_live_...` API key",
              "title": "X-Api-Key"
            },
            "description": "Your `sk_live_...` API key"
          }
        ],
        "responses": {
          "200": {
            "description": "Successful Response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "title": "Response Billing Portal Billing Portal Post"
                }
              }
            }
          },
          "422": {
            "description": "Validation Error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HTTPValidationError"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ActivityFeedEntryOut": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "action": {
            "type": "string",
            "title": "Action"
          },
          "summary": {
            "type": "string",
            "title": "Summary",
            "description": "Human-readable sentence describing the event"
          },
          "actor_email": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Actor Email",
            "description": "Email of the user who performed the action, if known"
          },
          "details": {
            "anyOf": [
              {
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Details"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "title": "Timestamp"
          }
        },
        "type": "object",
        "required": [
          "id",
          "action",
          "summary",
          "timestamp"
        ],
        "title": "ActivityFeedEntryOut"
      },
      "AffiliateApplyCreate": {
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 200,
            "minLength": 1,
            "title": "Name"
          },
          "email": {
            "type": "string",
            "maxLength": 320,
            "minLength": 3,
            "title": "Email"
          },
          "promotion_plan": {
            "type": "string",
            "maxLength": 2000,
            "title": "Promotion Plan",
            "description": "Free-text: how the applicant plans to promote OrbitAlert",
            "default": ""
          }
        },
        "type": "object",
        "required": [
          "name",
          "email"
        ],
        "title": "AffiliateApplyCreate"
      },
      "AffiliateOut": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "email": {
            "type": "string",
            "title": "Email"
          },
          "referral_code": {
            "type": "string",
            "title": "Referral Code"
          },
          "kind": {
            "type": "string",
            "title": "Kind"
          },
          "status": {
            "type": "string",
            "title": "Status"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At"
          }
        },
        "type": "object",
        "required": [
          "id",
          "name",
          "email",
          "referral_code",
          "kind",
          "status",
          "created_at"
        ],
        "title": "AffiliateOut"
      },
      "AffiliateStatsOut": {
        "properties": {
          "name": {
            "type": "string",
            "title": "Name"
          },
          "email": {
            "type": "string",
            "title": "Email"
          },
          "referral_code": {
            "type": "string",
            "title": "Referral Code"
          },
          "kind": {
            "type": "string",
            "title": "Kind"
          },
          "status": {
            "type": "string",
            "title": "Status"
          },
          "clicks_total": {
            "type": "integer",
            "title": "Clicks Total"
          },
          "conversions_pending": {
            "type": "integer",
            "title": "Conversions Pending"
          },
          "conversions_qualified": {
            "type": "integer",
            "title": "Conversions Qualified"
          },
          "conversions_paid": {
            "type": "integer",
            "title": "Conversions Paid"
          },
          "commission_pending_cents": {
            "type": "integer",
            "title": "Commission Pending Cents"
          },
          "commission_qualified_cents": {
            "type": "integer",
            "title": "Commission Qualified Cents"
          },
          "commission_paid_cents": {
            "type": "integer",
            "title": "Commission Paid Cents"
          }
        },
        "type": "object",
        "required": [
          "name",
          "email",
          "referral_code",
          "kind",
          "status",
          "clicks_total",
          "conversions_pending",
          "conversions_qualified",
          "conversions_paid",
          "commission_pending_cents",
          "commission_qualified_cents",
          "commission_paid_cents"
        ],
        "title": "AffiliateStatsOut"
      },
      "AlertConfigCreate": {
        "properties": {
          "satellite_name": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1,
            "title": "Satellite Name",
            "description": "Celestrak satellite name, e.g. `NOAA-20`, `ISS (ZARYA)`.",
            "examples": [
              "NOAA-20"
            ]
          },
          "norad_id": {
            "anyOf": [
              {
                "type": "integer",
                "minimum": 1.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Norad Id",
            "description": "NORAD catalog number \u2014 disambiguates if the name matches multiple objects.",
            "examples": [
              43013
            ]
          },
          "observer_lat": {
            "type": "number",
            "maximum": 90.0,
            "minimum": -90.0,
            "title": "Observer Lat",
            "description": "Ground station latitude (\u00b0).",
            "examples": [
              37.9838
            ]
          },
          "observer_lon": {
            "type": "number",
            "maximum": 180.0,
            "minimum": -180.0,
            "title": "Observer Lon",
            "description": "Ground station longitude (\u00b0).",
            "examples": [
              23.7275
            ]
          },
          "observer_alt_m": {
            "type": "number",
            "minimum": 0.0,
            "title": "Observer Alt M",
            "description": "Ground station altitude above sea level (m).",
            "default": 0.0
          },
          "webhook_url": {
            "type": "string",
            "maxLength": 2083,
            "minLength": 1,
            "format": "uri",
            "title": "Webhook Url",
            "description": "HTTPS endpoint that receives a POST before each upcoming pass.",
            "examples": [
              "https://your-server.com/webhooks/orbitalert"
            ]
          },
          "slack_webhook_url": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 2083,
                "minLength": 1,
                "format": "uri"
              },
              {
                "type": "null"
              }
            ],
            "title": "Slack Webhook Url",
            "description": "Pro plan only \u2014 mirrors delivery to a Slack Incoming Webhook."
          },
          "minutes_before": {
            "type": "integer",
            "maximum": 60.0,
            "minimum": 1.0,
            "title": "Minutes Before",
            "description": "How far ahead of AOS the webhook fires (1-60 minutes).",
            "default": 10
          },
          "min_elevation": {
            "type": "number",
            "maximum": 90.0,
            "minimum": 0.0,
            "title": "Min Elevation",
            "description": "Minimum peak elevation (\u00b0) for a pass to trigger an alert.",
            "default": 10.0
          }
        },
        "type": "object",
        "required": [
          "satellite_name",
          "observer_lat",
          "observer_lon",
          "webhook_url"
        ],
        "title": "AlertConfigCreate",
        "example": {
          "min_elevation": 10.0,
          "minutes_before": 10,
          "observer_alt_m": 100.0,
          "observer_lat": 37.9838,
          "observer_lon": 23.7275,
          "satellite_name": "NOAA-20",
          "webhook_url": "https://your-server.com/webhooks/orbitalert"
        }
      },
      "AlertConfigOut": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "satellite_name": {
            "type": "string",
            "title": "Satellite Name"
          },
          "norad_id": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Norad Id"
          },
          "observer_lat": {
            "type": "number",
            "title": "Observer Lat"
          },
          "observer_lon": {
            "type": "number",
            "title": "Observer Lon"
          },
          "observer_alt_m": {
            "type": "number",
            "title": "Observer Alt M"
          },
          "webhook_url": {
            "type": "string",
            "title": "Webhook Url"
          },
          "webhook_secret": {
            "type": "string",
            "title": "Webhook Secret"
          },
          "slack_webhook_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Slack Webhook Url"
          },
          "minutes_before": {
            "type": "integer",
            "title": "Minutes Before"
          },
          "min_elevation": {
            "type": "number",
            "title": "Min Elevation"
          },
          "active": {
            "type": "boolean",
            "title": "Active"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At"
          }
        },
        "type": "object",
        "required": [
          "id",
          "satellite_name",
          "norad_id",
          "observer_lat",
          "observer_lon",
          "observer_alt_m",
          "webhook_url",
          "webhook_secret",
          "minutes_before",
          "min_elevation",
          "active",
          "created_at"
        ],
        "title": "AlertConfigOut"
      },
      "AllowedIpCreate": {
        "properties": {
          "ip_address": {
            "type": "string",
            "maxLength": 64,
            "minLength": 3,
            "title": "Ip Address",
            "description": "A single IPv4/IPv6 address, or a CIDR range such as `203.0.113.0/24`.",
            "examples": [
              "203.0.113.42"
            ]
          },
          "description": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 200
              },
              {
                "type": "null"
              }
            ],
            "title": "Description",
            "examples": [
              "Office VPN"
            ]
          }
        },
        "type": "object",
        "required": [
          "ip_address"
        ],
        "title": "AllowedIpCreate"
      },
      "AllowedIpOut": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "ip_address": {
            "type": "string",
            "title": "Ip Address"
          },
          "description": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Description"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At"
          }
        },
        "type": "object",
        "required": [
          "id",
          "ip_address",
          "created_at"
        ],
        "title": "AllowedIpOut"
      },
      "AnalyticsSummaryOut": {
        "properties": {
          "weeks": {
            "type": "integer",
            "title": "Weeks"
          },
          "passes_per_week": {
            "items": {
              "$ref": "#/components/schemas/WeekCountOut"
            },
            "type": "array",
            "title": "Passes Per Week"
          },
          "avg_elevation_by_satellite": {
            "$ref": "#/components/schemas/AvgElevationBySatelliteOut"
          },
          "busiest_hours_utc": {
            "items": {
              "$ref": "#/components/schemas/HourCountOut"
            },
            "type": "array",
            "title": "Busiest Hours Utc"
          },
          "delivery_rate_by_month": {
            "items": {
              "$ref": "#/components/schemas/MonthDeliveryRateOut"
            },
            "type": "array",
            "title": "Delivery Rate By Month"
          }
        },
        "type": "object",
        "required": [
          "weeks",
          "passes_per_week",
          "avg_elevation_by_satellite",
          "busiest_hours_utc",
          "delivery_rate_by_month"
        ],
        "title": "AnalyticsSummaryOut"
      },
      "ApiKeyCreateOut": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "key": {
            "type": "string",
            "title": "Key"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At"
          }
        },
        "type": "object",
        "required": [
          "id",
          "key",
          "name",
          "created_at"
        ],
        "title": "ApiKeyCreateOut",
        "description": "Returned exactly once at creation \u2014 the key field is never stored."
      },
      "ApiKeyOut": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "key_prefix": {
            "type": "string",
            "title": "Key Prefix"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "active": {
            "type": "boolean",
            "title": "Active"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At"
          },
          "last_used_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Last Used At"
          },
          "requests_this_month": {
            "type": "integer",
            "title": "Requests This Month"
          }
        },
        "type": "object",
        "required": [
          "id",
          "key_prefix",
          "name",
          "active",
          "created_at",
          "requests_this_month"
        ],
        "title": "ApiKeyOut"
      },
      "AuditLogOut": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "user_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "User Id"
          },
          "action": {
            "type": "string",
            "title": "Action"
          },
          "details": {
            "anyOf": [
              {
                "type": "object"
              },
              {
                "type": "null"
              }
            ],
            "title": "Details"
          },
          "ip_address": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Ip Address"
          },
          "timestamp": {
            "type": "string",
            "format": "date-time",
            "title": "Timestamp"
          }
        },
        "type": "object",
        "required": [
          "id",
          "user_id",
          "action",
          "details",
          "ip_address",
          "timestamp"
        ],
        "title": "AuditLogOut"
      },
      "AvgElevationBySatelliteOut": {
        "properties": {
          "available": {
            "type": "boolean",
            "title": "Available"
          },
          "reason": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Reason",
            "description": "Set when available=false, explaining why no data could be computed"
          },
          "method": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Method",
            "description": "Set when available=true \u2014 explains this is a live forward-looking estimate, not historical data (pass elevations aren't persisted anywhere)."
          },
          "data": {
            "items": {
              "$ref": "#/components/schemas/SatelliteElevationOut"
            },
            "type": "array",
            "title": "Data"
          }
        },
        "type": "object",
        "required": [
          "available"
        ],
        "title": "AvgElevationBySatelliteOut"
      },
      "BatchPassQuery": {
        "properties": {
          "satellite": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1,
            "pattern": "^[A-Za-z0-9 ()\\-\\._/]+$",
            "title": "Satellite",
            "description": "Celestrak satellite name or NORAD ID",
            "examples": [
              "NOAA-20"
            ]
          },
          "lat": {
            "type": "number",
            "maximum": 90.0,
            "minimum": -90.0,
            "title": "Lat",
            "description": "Observer latitude (\u00b0)"
          },
          "lon": {
            "type": "number",
            "maximum": 180.0,
            "minimum": -180.0,
            "title": "Lon",
            "description": "Observer longitude (\u00b0)"
          },
          "alt_m": {
            "type": "number",
            "minimum": 0.0,
            "title": "Alt M",
            "description": "Observer altitude above sea level (m)",
            "default": 0.0
          },
          "hours": {
            "type": "number",
            "maximum": 72.0,
            "minimum": 1.0,
            "title": "Hours",
            "description": "Prediction window in hours (1-72)",
            "default": 24.0
          },
          "min_elevation": {
            "type": "number",
            "maximum": 90.0,
            "minimum": 0.0,
            "title": "Min Elevation",
            "description": "Minimum peak elevation (\u00b0) to include",
            "default": 10.0
          }
        },
        "type": "object",
        "required": [
          "satellite",
          "lat",
          "lon"
        ],
        "title": "BatchPassQuery"
      },
      "BatchPassRequest": {
        "properties": {
          "queries": {
            "items": {
              "$ref": "#/components/schemas/BatchPassQuery"
            },
            "type": "array",
            "maxItems": 20,
            "minItems": 1,
            "title": "Queries",
            "description": "Up to 20 satellite/ground-station combinations to predict in one call"
          }
        },
        "type": "object",
        "required": [
          "queries"
        ],
        "title": "BatchPassRequest"
      },
      "BatchPassResult": {
        "properties": {
          "satellite": {
            "type": "string",
            "title": "Satellite"
          },
          "lat": {
            "type": "number",
            "title": "Lat"
          },
          "lon": {
            "type": "number",
            "title": "Lon"
          },
          "passes": {
            "anyOf": [
              {
                "items": {
                  "$ref": "#/components/schemas/PassEventOut"
                },
                "type": "array"
              },
              {
                "type": "null"
              }
            ],
            "title": "Passes"
          },
          "error": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Error"
          }
        },
        "type": "object",
        "required": [
          "satellite",
          "lat",
          "lon"
        ],
        "title": "BatchPassResult"
      },
      "BillingStatusOut": {
        "properties": {
          "plan": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Plan"
          },
          "stripe_customer_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Stripe Customer Id"
          },
          "next_billing_date": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Next Billing Date"
          },
          "billing_period": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Billing Period"
          },
          "referral_code": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Referral Code"
          },
          "trial_plan": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Trial Plan"
          },
          "trial_ends_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Trial Ends At"
          },
          "discount_pct": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Discount Pct"
          },
          "discount_note": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Discount Note"
          }
        },
        "type": "object",
        "title": "BillingStatusOut"
      },
      "CheckoutRequest": {
        "properties": {
          "plan": {
            "type": "string",
            "pattern": "^(hobbyist|pro|research|enterprise)$",
            "title": "Plan",
            "examples": [
              "pro"
            ]
          },
          "interval": {
            "type": "string",
            "pattern": "^(monthly|yearly)$",
            "title": "Interval",
            "default": "monthly",
            "examples": [
              "monthly"
            ]
          }
        },
        "type": "object",
        "required": [
          "plan"
        ],
        "title": "CheckoutRequest"
      },
      "ConjunctionEventOut": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "primary_norad_id": {
            "type": "integer",
            "title": "Primary Norad Id"
          },
          "primary_name": {
            "type": "string",
            "title": "Primary Name"
          },
          "secondary_norad_id": {
            "type": "integer",
            "title": "Secondary Norad Id"
          },
          "secondary_name": {
            "type": "string",
            "title": "Secondary Name"
          },
          "tca_utc": {
            "type": "string",
            "format": "date-time",
            "title": "Tca Utc"
          },
          "miss_distance_km": {
            "type": "number",
            "title": "Miss Distance Km"
          },
          "relative_velocity_kms": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Relative Velocity Kms"
          },
          "probability": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Probability"
          }
        },
        "type": "object",
        "required": [
          "id",
          "primary_norad_id",
          "primary_name",
          "secondary_norad_id",
          "secondary_name",
          "tca_utc",
          "miss_distance_km"
        ],
        "title": "ConjunctionEventOut"
      },
      "ConstellationCoverageOut": {
        "properties": {
          "constellation": {
            "type": "string",
            "title": "Constellation"
          },
          "satellites_matched": {
            "type": "integer",
            "title": "Satellites Matched"
          },
          "truncated": {
            "type": "boolean",
            "title": "Truncated",
            "description": "True if more satellites matched this name than the 200-match cap \u2014 satellites_matched is the capped count, not the true total.",
            "default": false
          },
          "hourly_coverage": {
            "items": {
              "$ref": "#/components/schemas/HourlyCoverageOut"
            },
            "type": "array",
            "title": "Hourly Coverage"
          },
          "overall_coverage_pct": {
            "type": "number",
            "title": "Overall Coverage Pct"
          }
        },
        "type": "object",
        "required": [
          "constellation",
          "satellites_matched",
          "hourly_coverage",
          "overall_coverage_pct"
        ],
        "title": "ConstellationCoverageOut"
      },
      "CoverageEntry": {
        "properties": {
          "ground_station_id": {
            "type": "string",
            "title": "Ground Station Id"
          },
          "ground_station_name": {
            "type": "string",
            "title": "Ground Station Name"
          },
          "norad_id": {
            "type": "integer",
            "title": "Norad Id"
          },
          "satellite_name": {
            "type": "string",
            "title": "Satellite Name"
          },
          "azimuth": {
            "type": "number",
            "title": "Azimuth"
          },
          "elevation": {
            "type": "number",
            "title": "Elevation"
          }
        },
        "type": "object",
        "required": [
          "ground_station_id",
          "ground_station_name",
          "norad_id",
          "satellite_name",
          "azimuth",
          "elevation"
        ],
        "title": "CoverageEntry"
      },
      "CustomDomainOut": {
        "properties": {
          "domain": {
            "type": "string",
            "title": "Domain"
          },
          "verified": {
            "type": "boolean",
            "title": "Verified"
          },
          "cname_target": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Cname Target"
          }
        },
        "type": "object",
        "required": [
          "domain",
          "verified"
        ],
        "title": "CustomDomainOut"
      },
      "CustomDomainRequest": {
        "properties": {
          "domain": {
            "type": "string",
            "maxLength": 255,
            "minLength": 3,
            "title": "Domain",
            "description": "A subdomain you own, e.g. alerts.example.com",
            "examples": [
              "alerts.example.com"
            ]
          }
        },
        "type": "object",
        "required": [
          "domain"
        ],
        "title": "CustomDomainRequest"
      },
      "DopplerCurveOut": {
        "properties": {
          "satellite": {
            "type": "string",
            "title": "Satellite"
          },
          "aos_utc": {
            "type": "string",
            "format": "date-time",
            "title": "Aos Utc"
          },
          "los_utc": {
            "type": "string",
            "format": "date-time",
            "title": "Los Utc"
          },
          "tca_utc": {
            "type": "string",
            "format": "date-time",
            "title": "Tca Utc"
          },
          "frequency_mhz": {
            "type": "number",
            "title": "Frequency Mhz"
          },
          "points": {
            "items": {
              "$ref": "#/components/schemas/DopplerPointOut"
            },
            "type": "array",
            "title": "Points"
          }
        },
        "type": "object",
        "required": [
          "satellite",
          "aos_utc",
          "los_utc",
          "tca_utc",
          "frequency_mhz",
          "points"
        ],
        "title": "DopplerCurveOut"
      },
      "DopplerPointOut": {
        "properties": {
          "t_utc": {
            "type": "string",
            "format": "date-time",
            "title": "T Utc"
          },
          "elevation_deg": {
            "type": "number",
            "title": "Elevation Deg"
          },
          "range_km": {
            "type": "number",
            "title": "Range Km"
          },
          "doppler_shift_hz": {
            "type": "number",
            "title": "Doppler Shift Hz"
          },
          "shifted_frequency_mhz": {
            "type": "number",
            "title": "Shifted Frequency Mhz"
          }
        },
        "type": "object",
        "required": [
          "t_utc",
          "elevation_deg",
          "range_km",
          "doppler_shift_hz",
          "shifted_frequency_mhz"
        ],
        "title": "DopplerPointOut"
      },
      "EnterpriseContactRequest": {
        "properties": {
          "message": {
            "type": "string",
            "maxLength": 2000,
            "minLength": 1,
            "title": "Message",
            "examples": [
              "We're a 12-person research lab tracking a 6-satellite CubeSat constellation."
            ]
          },
          "billing_preference": {
            "type": "string",
            "enum": [
              "card",
              "invoice"
            ],
            "title": "Billing Preference",
            "description": "Whether the organization wants self-service card billing or an invoice/PO-based arrangement.",
            "default": "card"
          }
        },
        "type": "object",
        "required": [
          "message"
        ],
        "title": "EnterpriseContactRequest"
      },
      "GeneralContactRequest": {
        "properties": {
          "email": {
            "type": "string",
            "maxLength": 320,
            "minLength": 3,
            "title": "Email",
            "description": "Where we should reply.",
            "examples": [
              "engineer@example-sat.com"
            ]
          },
          "message": {
            "type": "string",
            "maxLength": 2000,
            "minLength": 1,
            "title": "Message",
            "examples": [
              "Does the API support batch pass predictions across multiple ground stations?"
            ]
          }
        },
        "type": "object",
        "required": [
          "email",
          "message"
        ],
        "title": "GeneralContactRequest",
        "description": "The site's general \"Contact / Support\" form (/contact) \u2014 for anyone with\na question that isn't specifically an Enterprise sales inquiry (bug\nreport, integration question, billing question, anything). Anonymous by\ndesign, same as PublicEnterpriseContactRequest, for the same reason: a\nvisitor with a question shouldn't have to sign up first to ask it."
      },
      "GroundStationCreate": {
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1,
            "title": "Name",
            "examples": [
              "Athens HQ"
            ]
          },
          "lat": {
            "type": "number",
            "maximum": 90.0,
            "minimum": -90.0,
            "title": "Lat",
            "examples": [
              37.9838
            ]
          },
          "lon": {
            "type": "number",
            "maximum": 180.0,
            "minimum": -180.0,
            "title": "Lon",
            "examples": [
              23.7275
            ]
          },
          "alt_m": {
            "type": "number",
            "minimum": 0.0,
            "title": "Alt M",
            "default": 0.0,
            "examples": [
              100.0
            ]
          }
        },
        "type": "object",
        "required": [
          "name",
          "lat",
          "lon"
        ],
        "title": "GroundStationCreate"
      },
      "GroundStationOut": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "lat": {
            "type": "number",
            "title": "Lat"
          },
          "lon": {
            "type": "number",
            "title": "Lon"
          },
          "alt_m": {
            "type": "number",
            "title": "Alt M"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At"
          }
        },
        "type": "object",
        "required": [
          "id",
          "name",
          "lat",
          "lon",
          "alt_m",
          "created_at"
        ],
        "title": "GroundStationOut"
      },
      "HTTPValidationError": {
        "properties": {
          "detail": {
            "items": {
              "$ref": "#/components/schemas/ValidationError"
            },
            "type": "array",
            "title": "Detail"
          }
        },
        "type": "object",
        "title": "HTTPValidationError"
      },
      "HourCountOut": {
        "properties": {
          "hour": {
            "type": "integer",
            "maximum": 23.0,
            "minimum": 0.0,
            "title": "Hour",
            "description": "UTC hour of day"
          },
          "count": {
            "type": "integer",
            "title": "Count"
          }
        },
        "type": "object",
        "required": [
          "hour",
          "count"
        ],
        "title": "HourCountOut"
      },
      "HourlyCoverageOut": {
        "properties": {
          "hour_start_utc": {
            "type": "string",
            "format": "date-time",
            "title": "Hour Start Utc"
          },
          "coverage_pct": {
            "type": "number",
            "title": "Coverage Pct"
          },
          "satellites_visible_peak": {
            "type": "integer",
            "title": "Satellites Visible Peak"
          }
        },
        "type": "object",
        "required": [
          "hour_start_utc",
          "coverage_pct",
          "satellites_visible_peak"
        ],
        "title": "HourlyCoverageOut"
      },
      "LinkBudgetOut": {
        "properties": {
          "eirp_dbw": {
            "type": "number",
            "title": "Eirp Dbw"
          },
          "fspl_db": {
            "type": "number",
            "title": "Fspl Db"
          },
          "atmospheric_loss_db": {
            "type": "number",
            "title": "Atmospheric Loss Db"
          },
          "total_path_loss_db": {
            "type": "number",
            "title": "Total Path Loss Db"
          },
          "received_power_dbw": {
            "type": "number",
            "title": "Received Power Dbw"
          },
          "received_power_dbm": {
            "type": "number",
            "title": "Received Power Dbm"
          },
          "link_margin_db": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Link Margin Db"
          },
          "link_closes": {
            "anyOf": [
              {
                "type": "boolean"
              },
              {
                "type": "null"
              }
            ],
            "title": "Link Closes"
          },
          "slant_range_km": {
            "type": "number",
            "title": "Slant Range Km",
            "description": "Geometry actually used \u2014 echoed back so a manual caller can sanity-check it and an automatic caller can see what was computed"
          },
          "elevation_deg": {
            "type": "number",
            "title": "Elevation Deg"
          },
          "computed_from": {
            "type": "string",
            "enum": [
              "manual",
              "next_pass"
            ],
            "title": "Computed From",
            "description": "'manual' if slant_range_km/elevation_deg were supplied directly, 'next_pass' if computed from satellite+lat+lon"
          },
          "tca_utc": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Tca Utc",
            "description": "Time of closest approach used for the calculation, only set when computed_from='next_pass'"
          }
        },
        "type": "object",
        "required": [
          "eirp_dbw",
          "fspl_db",
          "atmospheric_loss_db",
          "total_path_loss_db",
          "received_power_dbw",
          "received_power_dbm",
          "slant_range_km",
          "elevation_deg",
          "computed_from"
        ],
        "title": "LinkBudgetOut"
      },
      "LinkBudgetRequest": {
        "properties": {
          "frequency_mhz": {
            "type": "number",
            "maximum": 100000.0,
            "exclusiveMinimum": 0.0,
            "title": "Frequency Mhz",
            "description": "Carrier frequency (MHz)"
          },
          "tx_power_w": {
            "type": "number",
            "maximum": 100000.0,
            "exclusiveMinimum": 0.0,
            "title": "Tx Power W",
            "description": "Transmitter output power (W)"
          },
          "tx_antenna_gain_dbi": {
            "type": "number",
            "maximum": 100.0,
            "minimum": -10.0,
            "title": "Tx Antenna Gain Dbi",
            "description": "Transmit antenna gain (dBi)"
          },
          "rx_antenna_gain_dbi": {
            "type": "number",
            "maximum": 100.0,
            "minimum": -10.0,
            "title": "Rx Antenna Gain Dbi",
            "description": "Receive antenna gain (dBi)"
          },
          "slant_range_km": {
            "anyOf": [
              {
                "type": "number",
                "maximum": 500000.0,
                "exclusiveMinimum": 0.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Slant Range Km",
            "description": "Distance between transmitter and receiver (km). Required unless satellite+lat+lon are given."
          },
          "elevation_deg": {
            "anyOf": [
              {
                "type": "number",
                "maximum": 90.0,
                "minimum": 0.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Elevation Deg",
            "description": "Elevation angle, used for the atmospheric-attenuation estimate. Required unless satellite+lat+lon are given."
          },
          "satellite": {
            "anyOf": [
              {
                "type": "string",
                "maxLength": 100,
                "minLength": 1,
                "pattern": "^[A-Za-z0-9 ()\\-\\._/]+$"
              },
              {
                "type": "null"
              }
            ],
            "title": "Satellite",
            "description": "Celestrak satellite name or NORAD ID \u2014 computes range/elevation at the next pass's TCA instead of manual slant_range_km/elevation_deg."
          },
          "lat": {
            "anyOf": [
              {
                "type": "number",
                "maximum": 90.0,
                "minimum": -90.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Lat",
            "description": "Ground station latitude (\u00b0) \u2014 required with satellite"
          },
          "lon": {
            "anyOf": [
              {
                "type": "number",
                "maximum": 180.0,
                "minimum": -180.0
              },
              {
                "type": "null"
              }
            ],
            "title": "Lon",
            "description": "Ground station longitude (\u00b0) \u2014 required with satellite"
          },
          "alt_m": {
            "type": "number",
            "minimum": 0.0,
            "title": "Alt M",
            "description": "Ground station altitude above sea level (m)",
            "default": 0.0
          },
          "weather": {
            "type": "string",
            "enum": [
              "clear",
              "cloudy",
              "rain",
              "heavy_rain"
            ],
            "title": "Weather",
            "default": "clear"
          },
          "rx_sensitivity_dbm": {
            "anyOf": [
              {
                "type": "number"
              },
              {
                "type": "null"
              }
            ],
            "title": "Rx Sensitivity Dbm",
            "description": "Receiver sensitivity threshold (dBm) \u2014 if given, link_margin_db/link_closes are computed"
          }
        },
        "type": "object",
        "required": [
          "frequency_mhz",
          "tx_power_w",
          "tx_antenna_gain_dbi",
          "rx_antenna_gain_dbi"
        ],
        "title": "LinkBudgetRequest"
      },
      "MagicLinkRequest": {
        "properties": {
          "email": {
            "type": "string",
            "maxLength": 320,
            "minLength": 3,
            "title": "Email"
          }
        },
        "type": "object",
        "required": [
          "email"
        ],
        "title": "MagicLinkRequest"
      },
      "MemberRoleUpdate": {
        "properties": {
          "role": {
            "type": "string",
            "pattern": "^(admin|engineer|viewer)$",
            "title": "Role"
          }
        },
        "type": "object",
        "required": [
          "role"
        ],
        "title": "MemberRoleUpdate"
      },
      "MonthDeliveryRateOut": {
        "properties": {
          "month": {
            "type": "string",
            "title": "Month",
            "description": "Calendar month, e.g. '2026-06'"
          },
          "total": {
            "type": "integer",
            "title": "Total"
          },
          "successful": {
            "type": "integer",
            "title": "Successful"
          },
          "success_rate": {
            "type": "number",
            "title": "Success Rate",
            "description": "Percentage 0-100; 0.0 when total==0 (no data that month)"
          }
        },
        "type": "object",
        "required": [
          "month",
          "total",
          "successful",
          "success_rate"
        ],
        "title": "MonthDeliveryRateOut"
      },
      "OptimizeLocationCandidate": {
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 100,
            "minLength": 1,
            "title": "Name"
          },
          "lat": {
            "type": "number",
            "maximum": 90.0,
            "minimum": -90.0,
            "title": "Lat"
          },
          "lon": {
            "type": "number",
            "maximum": 180.0,
            "minimum": -180.0,
            "title": "Lon"
          },
          "alt_m": {
            "type": "number",
            "minimum": 0.0,
            "title": "Alt M",
            "default": 0.0
          }
        },
        "type": "object",
        "required": [
          "name",
          "lat",
          "lon"
        ],
        "title": "OptimizeLocationCandidate"
      },
      "OptimizeLocationRequest": {
        "properties": {
          "candidates": {
            "items": {
              "$ref": "#/components/schemas/OptimizeLocationCandidate"
            },
            "type": "array",
            "maxItems": 20,
            "minItems": 2,
            "title": "Candidates",
            "description": "Candidate ground-station locations to rank (2-20)"
          },
          "norad_id": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Norad Id",
            "description": "Track a single satellite by NORAD ID"
          },
          "constellation": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Constellation",
            "description": "Track every satellite matching this name substring (case-insensitive)"
          },
          "duration_hours": {
            "type": "number",
            "maximum": 72.0,
            "minimum": 1.0,
            "title": "Duration Hours",
            "default": 48.0
          },
          "min_elevation": {
            "type": "number",
            "maximum": 90.0,
            "minimum": 0.0,
            "title": "Min Elevation",
            "default": 10.0
          }
        },
        "type": "object",
        "required": [
          "candidates"
        ],
        "title": "OptimizeLocationRequest"
      },
      "OptimizeLocationResult": {
        "properties": {
          "name": {
            "type": "string",
            "title": "Name"
          },
          "lat": {
            "type": "number",
            "title": "Lat"
          },
          "lon": {
            "type": "number",
            "title": "Lon"
          },
          "alt_m": {
            "type": "number",
            "title": "Alt M"
          },
          "passes_per_day": {
            "type": "number",
            "title": "Passes Per Day"
          },
          "avg_max_elevation_deg": {
            "type": "number",
            "title": "Avg Max Elevation Deg"
          },
          "total_visible_minutes": {
            "type": "number",
            "title": "Total Visible Minutes"
          },
          "recommended": {
            "type": "boolean",
            "title": "Recommended",
            "default": false
          }
        },
        "type": "object",
        "required": [
          "name",
          "lat",
          "lon",
          "alt_m",
          "passes_per_day",
          "avg_max_elevation_deg",
          "total_visible_minutes"
        ],
        "title": "OptimizeLocationResult"
      },
      "OrganizationInviteCreate": {
        "properties": {
          "email": {
            "type": "string",
            "maxLength": 320,
            "minLength": 3,
            "title": "Email",
            "examples": [
              "teammate@example.com"
            ]
          },
          "role": {
            "type": "string",
            "pattern": "^(admin|engineer|viewer)$",
            "title": "Role",
            "examples": [
              "engineer"
            ]
          }
        },
        "type": "object",
        "required": [
          "email",
          "role"
        ],
        "title": "OrganizationInviteCreate"
      },
      "OrganizationInviteOut": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "email": {
            "type": "string",
            "title": "Email"
          },
          "role": {
            "type": "string",
            "title": "Role"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At"
          },
          "expires_at": {
            "type": "string",
            "format": "date-time",
            "title": "Expires At"
          }
        },
        "type": "object",
        "required": [
          "id",
          "email",
          "role",
          "created_at",
          "expires_at"
        ],
        "title": "OrganizationInviteOut"
      },
      "OrganizationMemberOut": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "user_id": {
            "type": "string",
            "title": "User Id"
          },
          "email": {
            "type": "string",
            "title": "Email"
          },
          "role": {
            "type": "string",
            "title": "Role"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At"
          }
        },
        "type": "object",
        "required": [
          "id",
          "user_id",
          "email",
          "role",
          "created_at"
        ],
        "title": "OrganizationMemberOut"
      },
      "OrganizationOut": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "plan": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Plan"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At"
          },
          "custom_domain": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Custom Domain"
          },
          "custom_domain_verified": {
            "type": "boolean",
            "title": "Custom Domain Verified",
            "default": false
          },
          "trial_ends_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Trial Ends At"
          }
        },
        "type": "object",
        "required": [
          "id",
          "name",
          "created_at"
        ],
        "title": "OrganizationOut"
      },
      "PassEventOut": {
        "properties": {
          "satellite": {
            "type": "string",
            "title": "Satellite"
          },
          "aos_utc": {
            "type": "string",
            "format": "date-time",
            "title": "Aos Utc"
          },
          "los_utc": {
            "type": "string",
            "format": "date-time",
            "title": "Los Utc"
          },
          "tca_utc": {
            "type": "string",
            "format": "date-time",
            "title": "Tca Utc"
          },
          "max_elevation_deg": {
            "type": "number",
            "title": "Max Elevation Deg"
          },
          "duration_seconds": {
            "type": "integer",
            "title": "Duration Seconds"
          },
          "azimuth_at_aos": {
            "type": "number",
            "title": "Azimuth At Aos"
          },
          "azimuth_at_los": {
            "type": "number",
            "title": "Azimuth At Los"
          }
        },
        "type": "object",
        "required": [
          "satellite",
          "aos_utc",
          "los_utc",
          "tca_utc",
          "max_elevation_deg",
          "duration_seconds",
          "azimuth_at_aos",
          "azimuth_at_los"
        ],
        "title": "PassEventOut"
      },
      "PublicEnterpriseContactRequest": {
        "properties": {
          "message": {
            "type": "string",
            "maxLength": 2000,
            "minLength": 1,
            "title": "Message",
            "examples": [
              "We're a 12-person research lab tracking a 6-satellite CubeSat constellation."
            ]
          },
          "billing_preference": {
            "type": "string",
            "enum": [
              "card",
              "invoice"
            ],
            "title": "Billing Preference",
            "description": "Whether the organization wants self-service card billing or an invoice/PO-based arrangement.",
            "default": "card"
          },
          "email": {
            "type": "string",
            "maxLength": 320,
            "minLength": 3,
            "title": "Email",
            "description": "Where we should reply.",
            "examples": [
              "ops@example-sat.com"
            ]
          }
        },
        "type": "object",
        "required": [
          "message",
          "email"
        ],
        "title": "PublicEnterpriseContactRequest",
        "description": "Anonymous variant used by prospects who don't have an account yet \u2014 the\nlanding page's \"Talk to sales\" CTA must not require signing up first, so\nthis carries a reply-to email instead of deriving it from the session."
      },
      "RateLimitIncreaseRequest": {
        "properties": {
          "requested_limit": {
            "type": "integer",
            "minimum": 1.0,
            "title": "Requested Limit",
            "description": "Desired requests/hour",
            "examples": [
              5000
            ]
          },
          "reason": {
            "type": "string",
            "maxLength": 2000,
            "minLength": 1,
            "title": "Reason",
            "examples": [
              "Bulk backfilling historical passes for a new ground station."
            ]
          }
        },
        "type": "object",
        "required": [
          "requested_limit",
          "reason"
        ],
        "title": "RateLimitIncreaseRequest"
      },
      "SatelliteElevationOut": {
        "properties": {
          "satellite_name": {
            "type": "string",
            "title": "Satellite Name"
          },
          "avg_max_elevation_deg": {
            "type": "number",
            "title": "Avg Max Elevation Deg"
          },
          "pass_count": {
            "type": "integer",
            "title": "Pass Count"
          }
        },
        "type": "object",
        "required": [
          "satellite_name",
          "avg_max_elevation_deg",
          "pass_count"
        ],
        "title": "SatelliteElevationOut"
      },
      "SatellitePositionLite": {
        "properties": {
          "norad_id": {
            "type": "integer",
            "title": "Norad Id"
          },
          "lat": {
            "type": "number",
            "title": "Lat"
          },
          "lon": {
            "type": "number",
            "title": "Lon"
          }
        },
        "type": "object",
        "required": [
          "norad_id",
          "lat",
          "lon"
        ],
        "title": "SatellitePositionLite",
        "description": "Minimal per-satellite payload for the full-catalog map layer (thousands\nof objects at once) \u2014 no name/altitude, since that layer is a dim,\nnon-interactive backdrop. Individually tracked/favorited satellites use\nthe fuller SatellitePositionOut via GET /satellites/positions instead."
      },
      "SatellitePositionOut": {
        "properties": {
          "norad_id": {
            "type": "integer",
            "title": "Norad Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "lat": {
            "type": "number",
            "title": "Lat"
          },
          "lon": {
            "type": "number",
            "title": "Lon"
          },
          "alt_km": {
            "type": "number",
            "title": "Alt Km"
          }
        },
        "type": "object",
        "required": [
          "norad_id",
          "name",
          "lat",
          "lon",
          "alt_km"
        ],
        "title": "SatellitePositionOut"
      },
      "SatelliteSearchResult": {
        "properties": {
          "norad_id": {
            "type": "integer",
            "title": "Norad Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          }
        },
        "type": "object",
        "required": [
          "norad_id",
          "name"
        ],
        "title": "SatelliteSearchResult"
      },
      "SatelliteTleOut": {
        "properties": {
          "norad_id": {
            "type": "integer",
            "title": "Norad Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "tle_line1": {
            "type": "string",
            "title": "Tle Line1"
          },
          "tle_line2": {
            "type": "string",
            "title": "Tle Line2"
          },
          "last_updated": {
            "type": "string",
            "format": "date-time",
            "title": "Last Updated"
          },
          "source": {
            "type": "string",
            "enum": [
              "catalog",
              "live"
            ],
            "title": "Source"
          }
        },
        "type": "object",
        "required": [
          "norad_id",
          "name",
          "tle_line1",
          "tle_line2",
          "last_updated",
          "source"
        ],
        "title": "SatelliteTleOut"
      },
      "TestWebhookOut": {
        "properties": {
          "success": {
            "type": "boolean",
            "title": "Success"
          },
          "status_code": {
            "anyOf": [
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ],
            "title": "Status Code"
          },
          "response_snippet": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Response Snippet"
          },
          "error": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Error"
          },
          "latency_ms": {
            "type": "integer",
            "title": "Latency Ms"
          }
        },
        "type": "object",
        "required": [
          "success",
          "latency_ms"
        ],
        "title": "TestWebhookOut",
        "description": "Result of a single synchronous test delivery \u2014 see POST /alerts/{id}/test-webhook."
      },
      "TestimonialOut": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "name": {
            "type": "string",
            "title": "Name"
          },
          "title": {
            "type": "string",
            "title": "Title"
          },
          "institution": {
            "type": "string",
            "title": "Institution"
          },
          "quote": {
            "type": "string",
            "title": "Quote"
          },
          "logo_url": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Logo Url"
          },
          "featured": {
            "type": "boolean",
            "title": "Featured"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At"
          }
        },
        "type": "object",
        "required": [
          "id",
          "name",
          "title",
          "institution",
          "quote",
          "featured",
          "created_at"
        ],
        "title": "TestimonialOut"
      },
      "UserCreate": {
        "properties": {
          "email": {
            "type": "string",
            "maxLength": 320,
            "minLength": 3,
            "title": "Email",
            "examples": [
              "alice@example.com"
            ]
          }
        },
        "type": "object",
        "required": [
          "email"
        ],
        "title": "UserCreate"
      },
      "UserOut": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "email": {
            "type": "string",
            "title": "Email"
          },
          "created_at": {
            "type": "string",
            "format": "date-time",
            "title": "Created At"
          },
          "organization_id": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Organization Id"
          },
          "role": {
            "anyOf": [
              {
                "type": "string"
              },
              {
                "type": "null"
              }
            ],
            "title": "Role"
          }
        },
        "type": "object",
        "required": [
          "id",
          "email",
          "created_at"
        ],
        "title": "UserOut"
      },
      "ValidationError": {
        "properties": {
          "loc": {
            "items": {
              "anyOf": [
                {
                  "type": "string"
                },
                {
                  "type": "integer"
                }
              ]
            },
            "type": "array",
            "title": "Location"
          },
          "msg": {
            "type": "string",
            "title": "Message"
          },
          "type": {
            "type": "string",
            "title": "Error Type"
          }
        },
        "type": "object",
        "required": [
          "loc",
          "msg",
          "type"
        ],
        "title": "ValidationError"
      },
      "WebhookHealthAlertOut": {
        "properties": {
          "id": {
            "type": "string",
            "title": "Id"
          },
          "alert_config_id": {
            "type": "string",
            "title": "Alert Config Id"
          },
          "organization_id": {
            "type": "string",
            "title": "Organization Id"
          },
          "failure_rate_recent": {
            "type": "number",
            "title": "Failure Rate Recent"
          },
          "failure_rate_prior": {
            "type": "number",
            "title": "Failure Rate Prior"
          },
          "triggered_at": {
            "type": "string",
            "format": "date-time",
            "title": "Triggered At"
          },
          "resolved_at": {
            "anyOf": [
              {
                "type": "string",
                "format": "date-time"
              },
              {
                "type": "null"
              }
            ],
            "title": "Resolved At"
          }
        },
        "type": "object",
        "required": [
          "id",
          "alert_config_id",
          "organization_id",
          "failure_rate_recent",
          "failure_rate_prior",
          "triggered_at"
        ],
        "title": "WebhookHealthAlertOut"
      },
      "WeekCountOut": {
        "properties": {
          "week": {
            "type": "string",
            "title": "Week",
            "description": "ISO week label, e.g. '2026-W24'"
          },
          "count": {
            "type": "integer",
            "title": "Count"
          }
        },
        "type": "object",
        "required": [
          "week",
          "count"
        ],
        "title": "WeekCountOut"
      }
    }
  },
  "tags": [
    {
      "name": "passes",
      "description": "Predict when a satellite will be visible over a ground station (SGP4 propagation)."
    },
    {
      "name": "alerts",
      "description": "Webhook alert configs \u2014 register a URL to be POSTed before each upcoming pass, and read delivery history. Shared across your organization."
    },
    {
      "name": "ground-stations",
      "description": "Save reusable observer locations (lat/lon/altitude) for use when creating alerts."
    },
    {
      "name": "organizations",
      "description": "Teams, roles (admin/engineer/viewer), invites, and the audit trail."
    },
    {
      "name": "billing",
      "description": "Stripe subscription checkout, customer portal, and current plan status. Organization-admin only."
    },
    {
      "name": "api-keys",
      "description": "Generate and manage the API keys used to authenticate against every endpoint above."
    },
    {
      "name": "users",
      "description": "Account bootstrap \u2014 called by the frontend right after sign-in."
    },
    {
      "name": "affiliates",
      "description": "Referral program: apply as a partner, track clicks/conversions, earn commission."
    },
    {
      "name": "space-weather",
      "description": "NOAA Kp-index feed \u2014 geomagnetic storm severity that can affect pass visibility."
    },
    {
      "name": "orbital-intelligence",
      "description": "Conjunction screening, link budget, ground-station placement, constellation coverage, and Doppler shift \u2014 advanced orbital analysis beyond single-satellite pass prediction."
    },
    {
      "name": "meta",
      "description": "Health, uptime/SLA history, and operational status \u2014 mostly unauthenticated."
    }
  ]
}