Space-Track rate limits and suspensions, explained
Space-Track asks API clients to stay under roughly 30 requests a minute and 300 an hour, and to query orbital data no more than once an hour. Those are not headers you can read at runtime; they are terms of use, enforced by a human process that can suspend an account. Almost every suspension comes from the same thing: a loop that queries one object at a time instead of one query that returns them all.
The limits, and what they are really asking for
- Under 30 requests a minute and under 300 an hour.
- Orbital data no more than once an hour: the underlying data is not regenerated faster than that, so polling harder returns the same rows.
- One session, reused. Log in once, keep the cookie, and log out when you are finished. Re-authenticating on every request counts as a request and doubles your rate for no benefit.
- Bulk queries over per-object queries. The API is a query language over a catalog, not a per-satellite lookup service, and it is happiest when you ask it for a set.
Source: Space-Track API documentation · Space-Track user agreement
Why accounts get suspended
Registration is not a formality; it is an identity attached to your traffic. That is the mechanism, and it is why the consequence of exceeding the limits is not a 429 you can retry but an account that stops working.
The pattern that causes it is nearly always structural rather than malicious. Somebody writes a loop over a list of 8,000 NORAD ids and fetches each one. At one request per id that is 8,000 requests, which is 26 times the hourly allowance, and it is also entirely unnecessary: a single query with a range predicate returns all 8,000 rows.
Ask for sets, not for objects
The single change that keeps almost everyone inside the limits is to move the filtering from your loop into the query. Space-Track supports predicates on the catalog, so one request can carry the whole selection.
# Do not do this. 8,000 requests where one would do.
for norad_id in norad_ids:
r = session.get(f"{BASE}/basicspacedata/query/class/gp/NORAD_CAT_ID/{norad_id}")
rows.append(r.json())# One request, one session, filtered server-side, then cached locally.
r = session.get(
f"{BASE}/basicspacedata/query/class/gp/"
"DECAY_DATE/null-val/EPOCH/>now-30/orderby/NORAD_CAT_ID/format/json"
)
rows = r.json() # thousands of objects, one requestWhen you cannot register at all
Registration is a genuine blocker for some projects, not an inconvenience: a public demo where you cannot ship credentials, a classroom where thirty people cannot each wait for approval, an open-source tool whose users should not need an account with a military organisation to run it.
That is the gap this catalog fills. Reads need no key and no approval, at 50 requests a day per IP address, and a free key raises that to 1,000 a day. The data is under an open licence, and going over the limit returns 429 with a retry hint rather than an email about your account.
When you should use Space-Track instead of us
When you need the official record. Space-Track is the origin of the data almost everyone else redistributes, and it publishes classes of information no downstream site has, including decay predictions and history that only the catalog owner holds. If your work depends on the authoritative version, or on data that has not been through anybody else’s pipeline, register and use it. Note also that their terms grant blanket approval to redistribute the basic items, element sets and the satellite catalog when properly cited, which is more permissive than people assume.
Source: Space-Track documentation
Try this catalog in thirty seconds
# Reads work with no key at 50 requests a day per IP. A free key raises it to 1,000.
curl -s 'https://www.orbitalwiki.com/api/v1/satellites/25544'
# Current orbital elements, as CCSDS OMM JSON
curl -s 'https://www.orbitalwiki.com/api/v1/satellites/25544/omm'
# Or as a two-line element set
curl -s 'https://www.orbitalwiki.com/api/v1/satellites/25544/tle?format=tle'import os
import requests
BASE = "https://www.orbitalwiki.com/api/v1"
# Optional. Without it you get 50 requests a day per IP; with it, 1,000 a day.
KEY = os.environ.get("ORBITALWIKI_API_KEY")
headers = {"Authorization": f"Bearer {KEY}"} if KEY else {}
r = requests.get(f"{BASE}/satellites", params={"limit": 100}, headers=headers, timeout=30)
r.raise_for_status()
page = r.json()
print(page["meta"]["total"], "objects in the catalog")
for sat in page["data"][:5]:
print(sat["norad_cat_id"], sat["name"], sat["operator"])
# Paginate with the cursor the response hands you, rather than guessing offsets.
# The whole catalog is far more than 50 requests, so a keyless run meets its
# limit part way through. Check every response, and stop when told to.
while page["meta"]["has_more"]:
r = requests.get(page["meta"]["next"], headers=headers, timeout=30)
if r.status_code == 429:
print("rate limited, retry in", r.headers.get("Retry-After"), "seconds")
break
r.raise_for_status()
page = r.json()Reads work without a key at 50 requests a day per IP address. A free key raises that to 1,000 requests a day, and going over returns 429 with a retry hint.
Checked against each provider’s own documentation on 2026-08-05. If a figure here is out of date, tell us at hello@orbitalwiki.com and we will correct it.