Predict when the International Space Station flies over your location โ and when you can actually see it.
A free, open-spirit rebuild of the retired open-notify.org/iss-pass.json API, extended to also tell you when a pass is visible to the naked eye โ not just above the horizon.
Example queries for Berlin, Germany (52.52ยฐN, 13.40ยฐE):
Interactive API explorer: /docs (Swagger UI) ยท Raw schema: /openapi.json
The API pulls fresh TLE orbital data for the ISS from CelesTrak (refreshed every 6 hours), runs the industry-standard SGP4 propagator via Skyfield, and computes when the ISS rises above your local horizon, reaches maximum elevation, and sets again.
For optical visibility it additionally checks two things at every moment of each pass:
Only when both conditions hold does the ISS actually appear as a bright moving star in the sky.
GET /iss-pass?lat={lat}&lon={lon}
| Name | Type | Default | Description |
|---|---|---|---|
lat | float | โ | Latitude in decimal degrees, โ90 to 90. required |
lon | float | โ | Longitude in decimal degrees, โ180 to 180. required |
alt | float | 0 | Elevation above sea level in meters. |
n | int | 5 | Max number of passes to return (1โ20). |
min_elevation | float | 10 | Minimum peak elevation in degrees. Lower values yield more passes, including ones barely skimming the horizon. |
visible_only | bool | false | Filter to passes that are actually observable with the naked eye. |
sun_alt_max | float | โ6 | Sun altitude threshold below which the observer counts as "dark" (degrees). โ6ยฐ = civil twilight. |
days_ahead | int | 10 | Forecast window in days (1โ14). |
All times are in UTC (ISO 8601 with Z suffix). Convert to local time on the client.
{
"satellite": "ISS (ZARYA)",
"tle_epoch": "2026-04-22T04:12:33Z",
"observer": { "lat": 52.52, "lon": 13.40, "elevation_m": 0 },
"generated_at": "2026-04-22T09:00:00Z",
"params": { ... },
"passes": [
{
"rise": { "time": "2026-04-22T19:32:04Z", "azimuth_deg": 236.1, "compass": "WSW" },
"culmination": { "time": "2026-04-22T19:35:29Z", "elevation_deg": 62.4 },
"set": { "time": "2026-04-22T19:38:51Z", "azimuth_deg": 48.7, "compass": "NE" },
"duration_sec": 407,
"above_horizon": true,
"visible": true,
"visible_start": "2026-04-22T19:32:04Z",
"visible_end": "2026-04-22T19:37:12Z",
"visible_duration_sec": 308
}
]
}
curl "https://iss-api.polluxlabs.io/iss-pass?lat=52.52&lon=13.40&visible_only=true"
const r = await fetch(
"https://iss-api.polluxlabs.io/iss-pass?lat=52.52&lon=13.40&visible_only=true"
);
const data = await r.json();
const next = data.passes[0];
console.log("Next visible pass:", new Date(next.rise.time).toLocaleString());
import httpx
r = httpx.get("https://iss-api.polluxlabs.io/iss-pass",
params={"lat": 52.52, "lon": 13.40, "visible_only": True})
for p in r.json()["passes"]:
print(p["rise"]["time"], "โ", p["set"]["time"],
f"(max {p['culmination']['elevation_deg']}ยฐ)")
#include <WiFi.h>
#include <HTTPClient.h>
#include <WiFiClientSecure.h>
#include <ArduinoJson.h>
const char* WIFI_SSID = "your-ssid";
const char* WIFI_PASS = "your-password";
const char* URL = "https://iss-api.polluxlabs.io/iss-pass?lat=52.52&lon=13.40&visible_only=true";
void setup() {
Serial.begin(115200);
WiFi.begin(WIFI_SSID, WIFI_PASS);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi connected");
WiFiClientSecure client;
client.setInsecure(); // skip cert check (fine for hobby projects)
HTTPClient http;
http.begin(client, URL);
int code = http.GET();
if (code == 200) {
JsonDocument doc;
deserializeJson(doc, http.getStream());
const char* rise = doc["passes"][0]["rise"]["time"];
int elevation = doc["passes"][0]["culmination"]["elevation_deg"];
Serial.printf("Next visible pass: %s (max %dยฐ)\n", rise, elevation);
} else {
Serial.printf("HTTP error: %d\n", code);
}
http.end();
}
void loop() {}
days_ahead is capped at 14.