{
  "info": {
    "name": "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.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "auth": {
    "type": "apikey",
    "apikey": [
      {
        "key": "key",
        "value": "X-API-Key",
        "type": "string"
      },
      {
        "key": "value",
        "value": "{{api_key}}",
        "type": "string"
      },
      {
        "key": "in",
        "value": "header",
        "type": "string"
      }
    ]
  },
  "variable": [
    {
      "key": "base_url",
      "value": "https://api.orbitalert.net",
      "type": "string"
    },
    {
      "key": "api_key",
      "value": "REPLACE_WITH_YOUR_API_KEY",
      "type": "string"
    }
  ],
  "item": [
    {
      "name": "affiliates",
      "item": [
        {
          "name": "Apply to become an affiliate partner",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/affiliates/apply",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "affiliates",
                "apply"
              ],
              "query": [],
              "variable": []
            },
            "description": "Public application form (W6 /affiliates/join). Starts as status=pending.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"string\",\n  \"email\": \"string\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Request an affiliate-portal login link",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/affiliates/magic-link",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "affiliates",
                "magic-link"
              ],
              "query": [],
              "variable": []
            },
            "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.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"string\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Affiliate's own profile and stats",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/affiliates/me",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "affiliates",
                "me"
              ],
              "query": [],
              "variable": []
            },
            "description": ""
          },
          "response": []
        }
      ]
    },
    {
      "name": "alerts",
      "item": [
        {
          "name": "Create webhook alert",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/alerts",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "alerts"
              ],
              "query": [],
              "variable": []
            },
            "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.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"min_elevation\": 10.0,\n  \"minutes_before\": 10,\n  \"observer_alt_m\": 100.0,\n  \"observer_lat\": 37.9838,\n  \"observer_lon\": 23.7275,\n  \"satellite_name\": \"NOAA-20\",\n  \"webhook_url\": \"https://your-server.com/webhooks/orbitalert\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "List webhook alerts",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/alerts",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "alerts"
              ],
              "query": [],
              "variable": []
            },
            "description": "Return all webhook alert configs (active and inactive) shared by the caller's organization."
          },
          "response": []
        },
        {
          "name": "Toggle alert active state",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/alerts/:alert_id",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "alerts",
                ":alert_id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "alert_id",
                  "value": "REPLACE_ALERT_ID"
                }
              ]
            },
            "description": "Toggle an alert config between active and inactive. Requires admin or engineer role."
          },
          "response": []
        },
        {
          "name": "Delete webhook alert",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/alerts/:alert_id",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "alerts",
                ":alert_id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "alert_id",
                  "value": "REPLACE_ALERT_ID"
                }
              ]
            },
            "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."
          },
          "response": []
        },
        {
          "name": "Send a single synchronous test webhook delivery",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/alerts/:alert_id/test-webhook",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "alerts",
                ":alert_id",
                "test-webhook"
              ],
              "query": [],
              "variable": [
                {
                  "key": "alert_id",
                  "value": "REPLACE_ALERT_ID"
                }
              ]
            },
            "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."
          },
          "response": []
        },
        {
          "name": "Recent webhook delivery logs",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/webhook-logs?limit=10",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "webhook-logs"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "10",
                  "description": "",
                  "disabled": true
                }
              ],
              "variable": []
            },
            "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."
          },
          "response": []
        },
        {
          "name": "Export full webhook delivery history as CSV (Pro plan feature)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/webhook-logs/export",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "webhook-logs",
                "export"
              ],
              "query": [],
              "variable": []
            },
            "description": "Download the caller's organization's complete webhook delivery history as CSV."
          },
          "response": []
        },
        {
          "name": "Predictive webhook delivery health alerts",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/webhook-health",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "webhook-health"
              ],
              "query": [],
              "variable": []
            },
            "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."
          },
          "response": []
        }
      ]
    },
    {
      "name": "analytics",
      "item": [
        {
          "name": "Customer-facing usage analytics",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/analytics/summary?weeks=8",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "analytics",
                "summary"
              ],
              "query": [
                {
                  "key": "weeks",
                  "value": "8",
                  "description": "Lookback window in weeks",
                  "disabled": true
                }
              ],
              "variable": []
            },
            "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)."
          },
          "response": []
        },
        {
          "name": "Download a monthly usage PDF report (Research/Enterprise)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/reports/monthly.pdf?month=2026-06",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "reports",
                "monthly.pdf"
              ],
              "query": [
                {
                  "key": "month",
                  "value": "2026-06",
                  "description": "Report month as YYYY-MM. Defaults to the previous full calendar month.",
                  "disabled": true
                }
              ],
              "variable": []
            },
            "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."
          },
          "response": []
        }
      ]
    },
    {
      "name": "api-keys",
      "item": [
        {
          "name": "Generate API key",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api-keys?name=Default",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api-keys"
              ],
              "query": [
                {
                  "key": "name",
                  "value": "Default",
                  "description": "",
                  "disabled": true
                }
              ],
              "variable": []
            },
            "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."
          },
          "response": []
        },
        {
          "name": "List API keys",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api-keys",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api-keys"
              ],
              "query": [],
              "variable": []
            },
            "description": "Return all active API keys for the authenticated user (prefix only \u2014 never full key)."
          },
          "response": []
        },
        {
          "name": "Revoke API key",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/api-keys/:key_id",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "api-keys",
                ":key_id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "key_id",
                  "value": "REPLACE_KEY_ID"
                }
              ]
            },
            "description": "Soft-delete (revoke) an API key.  The key cannot be reinstated."
          },
          "response": []
        }
      ]
    },
    {
      "name": "billing",
      "item": [
        {
          "name": "Create Stripe Checkout session",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/billing/create-checkout",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "billing",
                "create-checkout"
              ],
              "query": [],
              "variable": []
            },
            "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.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"plan\": \"pro\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Current billing status",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/billing/status",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "billing",
                "status"
              ],
              "query": [],
              "variable": []
            },
            "description": "Return the caller's organization's current plan, Stripe customer ID, and next billing date."
          },
          "response": []
        },
        {
          "name": "Create Stripe customer portal session",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/billing/portal",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "billing",
                "portal"
              ],
              "query": [],
              "variable": []
            },
            "description": "Create a Stripe Billing Portal session so an org admin can manage invoices,\nchange payment method, or cancel.  Returns ``{portal_url}``."
          },
          "response": []
        }
      ]
    },
    {
      "name": "ground-stations",
      "item": [
        {
          "name": "List saved ground stations",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/ground-stations",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "ground-stations"
              ],
              "query": [],
              "variable": []
            },
            "description": "Return the caller's organization's saved observer locations."
          },
          "response": []
        },
        {
          "name": "Save a ground station",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/ground-stations",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "ground-stations"
              ],
              "query": [],
              "variable": []
            },
            "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.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"name\": \"Athens HQ\",\n  \"lat\": 37.9838,\n  \"lon\": 23.7275\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Delete a ground station",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/ground-stations/:station_id",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "ground-stations",
                ":station_id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "station_id",
                  "value": "REPLACE_STATION_ID"
                }
              ]
            },
            "description": "Requires the admin or engineer role."
          },
          "response": []
        }
      ]
    },
    {
      "name": "meta",
      "item": [
        {
          "name": "Health check",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/health",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "health"
              ],
              "query": [],
              "variable": []
            },
            "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."
          },
          "response": []
        },
        {
          "name": "Webhook worker health",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/worker-status",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "worker-status"
              ],
              "query": [],
              "variable": []
            },
            "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."
          },
          "response": []
        },
        {
          "name": "Public status page data",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/status",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "status"
              ],
              "query": [],
              "variable": []
            },
            "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."
          },
          "response": []
        },
        {
          "name": "Uptime history as raw JSON",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/status/history?days=90",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "status",
                "history"
              ],
              "query": [
                {
                  "key": "days",
                  "value": "90",
                  "description": "How many days of history to return",
                  "disabled": true
                }
              ],
              "variable": []
            },
            "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."
          },
          "response": []
        },
        {
          "name": "SLA uptime history",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/sla",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "sla"
              ],
              "query": [],
              "variable": []
            },
            "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."
          },
          "response": []
        },
        {
          "name": "Embeddable uptime badge (SVG)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/badge/uptime.svg?days=30",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "badge",
                "uptime.svg"
              ],
              "query": [
                {
                  "key": "days",
                  "value": "30",
                  "description": "Uptime window in days",
                  "disabled": true
                }
              ],
              "variable": []
            },
            "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."
          },
          "response": []
        },
        {
          "name": "Changelog entries as JSON",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/changelog.json",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "changelog.json"
              ],
              "query": [],
              "variable": []
            },
            "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."
          },
          "response": []
        },
        {
          "name": "Changelog as an RSS 2.0 feed",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/changelog.xml",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "changelog.xml"
              ],
              "query": [],
              "variable": []
            },
            "description": "Public, unauthenticated. Hand-built RSS 2.0 XML \u2014 simple enough not to need a library."
          },
          "response": []
        },
        {
          "name": "Featured testimonials",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/testimonials",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "testimonials"
              ],
              "query": [],
              "variable": []
            },
            "description": "Public \u2014 returns only testimonials marked featured=true, for the landing page."
          },
          "response": []
        }
      ]
    },
    {
      "name": "orbital-intelligence",
      "item": [
        {
          "name": "Predicted close approaches for your tracked satellites (Research/Enterprise)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/conjunctions",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "conjunctions"
              ],
              "query": [],
              "variable": []
            },
            "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."
          },
          "response": []
        },
        {
          "name": "RF link budget calculator (free-space path loss + atmospheric attenuation)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/link-budget",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "link-budget"
              ],
              "query": [],
              "variable": []
            },
            "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.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"frequency_mhz\": 0.0,\n  \"tx_power_w\": 0.0,\n  \"tx_antenna_gain_dbi\": 0.0,\n  \"rx_antenna_gain_dbi\": 0.0\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Rank candidate ground-station locations by predicted satellite visibility",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/optimize-location",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "optimize-location"
              ],
              "query": [],
              "variable": []
            },
            "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`.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"candidates\": [\n    null\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Aggregate hourly visibility coverage for every satellite matching a constellation name",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/constellation/:name/coverage?lat=0.0&lon=0.0&alt_m=0.0&hours=24.0&min_elevation=10.0",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "constellation",
                ":name",
                "coverage"
              ],
              "query": [
                {
                  "key": "lat",
                  "value": "0.0",
                  "description": "Observer latitude (\u00b0)",
                  "disabled": false
                },
                {
                  "key": "lon",
                  "value": "0.0",
                  "description": "Observer longitude (\u00b0)",
                  "disabled": false
                },
                {
                  "key": "alt_m",
                  "value": "0.0",
                  "description": "Observer altitude above sea level (m)",
                  "disabled": true
                },
                {
                  "key": "hours",
                  "value": "24.0",
                  "description": "Prediction window in hours (1-72)",
                  "disabled": true
                },
                {
                  "key": "min_elevation",
                  "value": "10.0",
                  "description": "Minimum peak elevation (\u00b0) to count as visible",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "name",
                  "value": "REPLACE_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."
          },
          "response": []
        },
        {
          "name": "Doppler shift curve across the next qualifying pass",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/passes/doppler?satellite=string&lat=0.0&lon=0.0&alt_m=0.0&frequency_mhz=0.0&step_seconds=5&hours=24.0&min_elevation=10.0",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "passes",
                "doppler"
              ],
              "query": [
                {
                  "key": "satellite",
                  "value": "string",
                  "description": "Celestrak satellite name (`NOAA-20`) or NORAD ID (`43013`)",
                  "disabled": false
                },
                {
                  "key": "lat",
                  "value": "0.0",
                  "description": "Observer latitude (\u00b0)",
                  "disabled": false
                },
                {
                  "key": "lon",
                  "value": "0.0",
                  "description": "Observer longitude (\u00b0)",
                  "disabled": false
                },
                {
                  "key": "alt_m",
                  "value": "0.0",
                  "description": "Observer altitude above sea level (m)",
                  "disabled": true
                },
                {
                  "key": "frequency_mhz",
                  "value": "0.0",
                  "description": "Carrier frequency (MHz)",
                  "disabled": false
                },
                {
                  "key": "step_seconds",
                  "value": "5",
                  "description": "Time step across the pass (s)",
                  "disabled": true
                },
                {
                  "key": "hours",
                  "value": "24.0",
                  "description": "Search window for the next qualifying pass",
                  "disabled": true
                },
                {
                  "key": "min_elevation",
                  "value": "10.0",
                  "description": "Minimum peak elevation (\u00b0) to qualify",
                  "disabled": true
                }
              ],
              "variable": []
            },
            "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."
          },
          "response": []
        }
      ]
    },
    {
      "name": "organizations",
      "item": [
        {
          "name": "Current organization",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/organizations/me",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "organizations",
                "me"
              ],
              "query": [],
              "variable": []
            },
            "description": "Return the caller's organization (plan, name, creation date)."
          },
          "response": []
        },
        {
          "name": "List organization members",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/organizations/members",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "organizations",
                "members"
              ],
              "query": [],
              "variable": []
            },
            "description": "Return every member of the caller's organization and their role. Any role may view."
          },
          "response": []
        },
        {
          "name": "Change a member's role",
          "request": {
            "method": "PATCH",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/organizations/members/:member_id",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "organizations",
                "members",
                ":member_id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "member_id",
                  "value": "REPLACE_MEMBER_ID"
                }
              ]
            },
            "description": "Requires the admin role. Cannot demote the organization's last admin.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"role\": \"string\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Remove a member",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/organizations/members/:member_id",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "organizations",
                "members",
                ":member_id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "member_id",
                  "value": "REPLACE_MEMBER_ID"
                }
              ]
            },
            "description": "Requires the admin role. Cannot remove the organization's last admin."
          },
          "response": []
        },
        {
          "name": "Invite a teammate",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/organizations/invites",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "organizations",
                "invites"
              ],
              "query": [],
              "variable": []
            },
            "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.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"teammate@example.com\",\n  \"role\": \"engineer\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Organization audit trail",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/audit-logs?limit=50",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "audit-logs"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50",
                  "description": "",
                  "disabled": true
                }
              ],
              "variable": []
            },
            "description": "Return the caller's organization's audit trail, most recent first. Requires the admin role."
          },
          "response": []
        },
        {
          "name": "List the organization's IP allowlist",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/organizations/allowed-ips",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "organizations",
                "allowed-ips"
              ],
              "query": [],
              "variable": []
            },
            "description": ""
          },
          "response": []
        },
        {
          "name": "Add an IP (or CIDR range) to the allowlist",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/organizations/allowed-ips",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "organizations",
                "allowed-ips"
              ],
              "query": [],
              "variable": []
            },
            "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.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"ip_address\": \"203.0.113.42\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Remove an IP from the allowlist",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/organizations/allowed-ips/:allowed_ip_id",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "organizations",
                "allowed-ips",
                ":allowed_ip_id"
              ],
              "query": [],
              "variable": [
                {
                  "key": "allowed_ip_id",
                  "value": "REPLACE_ALLOWED_IP_ID"
                }
              ]
            },
            "description": ""
          },
          "response": []
        },
        {
          "name": "Register a custom domain for white-label branding (Enterprise only)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/organizations/custom-domain",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "organizations",
                "custom-domain"
              ],
              "query": [],
              "variable": []
            },
            "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.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"domain\": \"alerts.example.com\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Remove the organization's custom domain",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/organizations/custom-domain",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "organizations",
                "custom-domain"
              ],
              "query": [],
              "variable": []
            },
            "description": ""
          },
          "response": []
        },
        {
          "name": "Verify the pending custom domain's CNAME record",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/organizations/custom-domain/verify",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "organizations",
                "custom-domain",
                "verify"
              ],
              "query": [],
              "variable": []
            },
            "description": ""
          },
          "response": []
        },
        {
          "name": "Request a rate-limit increase for your organization (Enterprise only)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/organizations/rate-limit-request",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "organizations",
                "rate-limit-request"
              ],
              "query": [],
              "variable": []
            },
            "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.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"requested_limit\": 5000,\n  \"reason\": \"Bulk backfilling historical passes for a new ground station.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Request to talk to sales about the Enterprise plan",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/organizations/enterprise-contact",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "organizations",
                "enterprise-contact"
              ],
              "query": [],
              "variable": []
            },
            "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.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"message\": \"We're a 12-person research lab tracking a 6-satellite CubeSat constellation.\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Request to talk to sales about the Enterprise plan (no account required)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/enterprise-contact/public",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "enterprise-contact",
                "public"
              ],
              "query": [],
              "variable": []
            },
            "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.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"message\": \"We're a 12-person research lab tracking a 6-satellite CubeSat constellation.\",\n  \"email\": \"ops@example-sat.com\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "General contact / support form (no account required)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/contact",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "contact"
              ],
              "query": [],
              "variable": []
            },
            "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).",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"engineer@example-sat.com\",\n  \"message\": \"Does the API support batch pass predictions across multiple ground stations?\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Export all of your organization's data (GDPR data portability)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/account/export-data",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "account",
                "export-data"
              ],
              "query": [],
              "variable": []
            },
            "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)."
          },
          "response": []
        },
        {
          "name": "Download a personalized, signable Data Processing Agreement (PDF)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/account/dpa-pdf",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "account",
                "dpa-pdf"
              ],
              "query": [],
              "variable": []
            },
            "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."
          },
          "response": []
        },
        {
          "name": "Request permanent deletion of your organization (GDPR erasure)",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/account/delete-request",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "account",
                "delete-request"
              ],
              "query": [],
              "variable": []
            },
            "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."
          },
          "response": []
        },
        {
          "name": "Confirm permanent deletion of your organization (GDPR erasure)",
          "request": {
            "method": "DELETE",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/account?token=string",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "account"
              ],
              "query": [
                {
                  "key": "token",
                  "value": "string",
                  "description": "The token from the confirmation email",
                  "disabled": false
                }
              ],
              "variable": []
            },
            "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."
          },
          "response": []
        },
        {
          "name": "Friendly team activity feed (built on the audit log)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/activity-feed?limit=50",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "activity-feed"
              ],
              "query": [
                {
                  "key": "limit",
                  "value": "50",
                  "description": "",
                  "disabled": true
                }
              ],
              "variable": []
            },
            "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."
          },
          "response": []
        }
      ]
    },
    {
      "name": "passes",
      "item": [
        {
          "name": "Predict satellite passes",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/passes?satellite=NOAA-20&lat=0.0&lon=0.0&alt_m=0.0&hours=24.0&min_elevation=10.0",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "passes"
              ],
              "query": [
                {
                  "key": "satellite",
                  "value": "NOAA-20",
                  "description": "Celestrak satellite name (`NOAA-20`) or NORAD ID (`43013`)",
                  "disabled": false
                },
                {
                  "key": "lat",
                  "value": "0.0",
                  "description": "Observer latitude (\u00b0)",
                  "disabled": false
                },
                {
                  "key": "lon",
                  "value": "0.0",
                  "description": "Observer longitude (\u00b0)",
                  "disabled": false
                },
                {
                  "key": "alt_m",
                  "value": "0.0",
                  "description": "Observer altitude above sea level (m)",
                  "disabled": true
                },
                {
                  "key": "hours",
                  "value": "24.0",
                  "description": "Prediction window in hours (1\u201372)",
                  "disabled": true
                },
                {
                  "key": "min_elevation",
                  "value": "10.0",
                  "description": "Minimum peak elevation (\u00b0) to include",
                  "disabled": true
                }
              ],
              "variable": []
            },
            "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."
          },
          "response": []
        },
        {
          "name": "Predict passes for up to 20 satellite/ground-station combinations in one call",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/passes/batch",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "passes",
                "batch"
              ],
              "query": [],
              "variable": []
            },
            "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.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"queries\": [\n    null\n  ]\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Search the satellite catalog (autocomplete)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/satellites/search?q=&limit=20",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "satellites",
                "search"
              ],
              "query": [
                {
                  "key": "q",
                  "value": "",
                  "description": "Name substring or exact NORAD ID",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "20",
                  "description": "",
                  "disabled": true
                }
              ],
              "variable": []
            },
            "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."
          },
          "response": []
        },
        {
          "name": "Search the satellite catalog (autocomplete, no account required)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/satellites/search/public?q=&limit=20",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "satellites",
                "search",
                "public"
              ],
              "query": [
                {
                  "key": "q",
                  "value": "",
                  "description": "Name substring or exact NORAD ID",
                  "disabled": true
                },
                {
                  "key": "limit",
                  "value": "20",
                  "description": "",
                  "disabled": true
                }
              ],
              "variable": []
            },
            "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."
          },
          "response": []
        },
        {
          "name": "Current ground-track position for a set of satellites (map display)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/satellites/positions?ids=25544,43013,33591",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "satellites",
                "positions"
              ],
              "query": [
                {
                  "key": "ids",
                  "value": "25544,43013,33591",
                  "description": "Comma-separated NORAD IDs, up to 50",
                  "disabled": false
                }
              ],
              "variable": []
            },
            "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."
          },
          "response": []
        },
        {
          "name": "Current ground-track positions for the entire catalog (map backdrop layer)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/satellites/positions/all",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "satellites",
                "positions",
                "all"
              ],
              "query": [],
              "variable": []
            },
            "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."
          },
          "response": []
        },
        {
          "name": "Raw TLE for a satellite (Pro plan; live lookup on Enterprise)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/satellites/:norad_id/tle?fresh=False",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "satellites",
                ":norad_id",
                "tle"
              ],
              "query": [
                {
                  "key": "fresh",
                  "value": "False",
                  "description": "Bypass the 24h catalog cache with a live Celestrak/SatNOGS lookup (Enterprise only)",
                  "disabled": true
                }
              ],
              "variable": [
                {
                  "key": "norad_id",
                  "value": "REPLACE_NORAD_ID"
                }
              ]
            },
            "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."
          },
          "response": []
        },
        {
          "name": "Which of your ground stations currently see which of your tracked satellites (Enterprise)",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/coverage",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "coverage"
              ],
              "query": [],
              "variable": []
            },
            "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."
          },
          "response": []
        }
      ]
    },
    {
      "name": "space-weather",
      "item": [
        {
          "name": "Current NOAA Kp index",
          "request": {
            "method": "GET",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/space-weather",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "space-weather"
              ],
              "query": [],
              "variable": []
            },
            "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+                 |"
          },
          "response": []
        }
      ]
    },
    {
      "name": "users",
      "item": [
        {
          "name": "Clerk post-login user sync",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/users/sync",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "users",
                "sync"
              ],
              "query": [],
              "variable": []
            },
            "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.",
            "body": {
              "mode": "raw",
              "raw": "{}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        },
        {
          "name": "Create user account",
          "request": {
            "method": "POST",
            "header": [
              {
                "key": "Content-Type",
                "value": "application/json"
              }
            ],
            "url": {
              "raw": "{{base_url}}/users",
              "host": [
                "{{base_url}}"
              ],
              "path": [
                "users"
              ],
              "query": [],
              "variable": []
            },
            "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.",
            "body": {
              "mode": "raw",
              "raw": "{\n  \"email\": \"alice@example.com\"\n}",
              "options": {
                "raw": {
                  "language": "json"
                }
              }
            }
          },
          "response": []
        }
      ]
    }
  ]
}