CelesTrak is returning 403 Forbidden
A 403 from CelesTrak is almost never about you personally. CelesTrak documents two causes: you are asking too often, or you are asking at a long-outdated URL, which usually means software written a decade or more ago. It is a 501(c)(3) non-profit serving orbital data to the entire world for free, and it protects itself from both. Check your URLs first, because that fix is one line. Then slow down, cache what you already have, identify your client honestly, and stop requesting one object at a time when a group file would do.
What actually triggers it
Check the URL before you assume it is rate limiting. CelesTrak sends 301, 403, 404 and 50x for legacy queries it has been redirecting for years, and says that cohort is usually running software written a decade or more ago. Confirm you are on https://celestrak.org and using a current gp.php, SupGP or SATCAT query before you rewrite your caching layer, because if that is the problem the fix is a one-line URL change.
The other cause is frequency. CelesTrak publishes a usage policy rather than a per-client limit you can read at runtime, which surprises people who expect an X-RateLimit header to tell them where the line is. There is no such header. The policy states cadences instead, GP data every 2 hours and the directory endpoints no more than once an hour, and the block is applied at the server, so the first you hear about it is a 403.
In practice the patterns that earn one are recognisable. Polling the same object every few seconds. Fetching thousands of single-object queries in a loop when one group file contains all of them. A default or absent User-Agent, so there is nobody to contact when your script misbehaves. Re-downloading a file that has not changed since your last request an hour ago.
How to be a good citizen of a free service
- Send a real User-Agent with a contact address. "python-requests/2.31" is anonymous; "MyProject/1.0 (+https://example.com; me@example.com)" is a name somebody can reach before they reach for a block.
- Fetch group files, not individual objects. One request for the whole active group gives you thousands of element sets; ten thousand CATNR requests give you the same data and a 403.
- Respect the update cadence. CelesTrak regenerates GP data once every 2 hours and asks you to download once per update; the directory (jsonDir) endpoints, no more than once an hour. Polling every minute cannot make the data fresher; it can only cost somebody else their bandwidth.
- Cache on a timer, because there is nothing to revalidate against. gp.php sends no ETag, no Last-Modified and no Cache-Control, and it answers an If-Modified-Since request with a full 200 and the whole body. A local time-to-live matched to the 2-hour cadence is the mechanism, not conditional requests.
- Stop on failure, do not back off and carry on. The policy asks that machine-to-machine software "immediately stop querying when it receives any non-HTTP 200 responses and report the results to a human for investigation", and warns that repeatedly ignoring them "will end up sending your IP address to the firewall". An unattended retry loop is still a retry loop, however polite its backoff.
Source: CelesTrak usage policy
CelesTrak is free because somebody pays for it
It is worth saying plainly: CelesTrak has served this community for decades, is run as a non-profit, and asks nothing of the people who depend on it. The bandwidth is not free to the person providing it. If your project relies on it, the honest response to a rate limit is to fetch less and to contribute, not to route around it.
Source: Support CelesTrak
What to do while you are blocked
A block usually clears once the behaviour that caused it stops. Fix the fetch pattern first. If you need a second source in the meantime, or you want a keyed API with a published limit and an error you can handle programmatically, this catalog is built from CelesTrak data and serves it under an open licence.
Our limits are stated rather than implied: reads work with no key at 50 requests a day per IP address, a free key raises that to 1,000 requests a day, and going over returns HTTP 429 with a header telling you when to retry, not a silent 403.
When you should use CelesTrak instead of us
For the orbital elements themselves. CelesTrak is upstream of this catalog and updates faster than we do, so if you are propagating orbits, screening conjunctions, or you simply need the freshest element set, take it from CelesTrak. It also publishes classes of data we do not republish at all, including supplemental element sets and conjunction reports. This catalog is the better tool when the question is about the object rather than its orbit: who operates it, what it is, and which source each field came from.
Source: CelesTrak data products
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.