Unofficial free self-hosted API for JetPhotos.com aircraft photo search.
Returns structured JSON for registration / aircraft / airline / photographer searches.
No paid scraper keys required.
Run it on your computer or a VPS withnpm start.
| Check | Result |
|---|---|
Local self-host (npm start) |
Works |
| Paid proxy required | No |
Cloudflare Workers edge deploy (npm run deploy) |
Often blocked by JetPhotos (403) |
Verified locally against a live JetPhotos search (Boeing 747) returning photo JSON with meta.free: true and meta.mode: "workerd-direct".
JetPhotos is Cloudflare-protected. Direct scrapes from Cloudflare’s edge (and many plain curl/Node fetches) get 403 Forbidden.
This project’s free path is:
Client → this API on your PC/VPS (Wrangler/workerd) → JetPhotos HTML → JSON
| Where you run it | Free? | Usually works? | Notes |
|---|---|---|---|
| Your computer (recommended) | yes | yes | Best option. Uses your residential network. |
| VPS | VPS may cost money | maybe | Some datacenter IPs get Cloudflare 403. Test first. |
| Cloudflare Workers edge deploy | yes | often no | JetPhotos blocks many Worker edge requests. |
On the same home network:
- plain
curl/ Nodefetch→ often Cloudflare 403 - this app via Wrangler/workerd → often works
So the free server runtime is Wrangler (workerd), not a raw Node HTTP scraper and not a paid proxy.
- Node.js 18+
- npm
- Network access to
www.jetphotos.com
git clone https://github.com/roowus/Jetphotos-API.git
cd Jetphotos-API
npm install
npm startLeave that terminal running. In another terminal:
# health
curl -s http://127.0.0.1:8787/health | jq .
# sample search
curl -s "http://127.0.0.1:8787/?page=1&sort-order=0&keywords=Boeing%20747&keywords-type=aircraft&keywords-contain=3" \
| jq '{count, free: .meta.free, mode: .meta.mode, first: .photos[0].registration}'
# automated smoke test
npm run test:api{
"count": 52,
"free": true,
"mode": "workerd-direct",
"first": "N452PA"
}Local base URL:
http://127.0.0.1:8787
If health works but search returns 403, your current IP is blocked by JetPhotos/Cloudflare. Try another network or VPS IP.
{
"ok": true,
"free": true,
"mode": "workerd-direct"
}| Parameter | Description | Example |
|---|---|---|
page |
Page number | 1 |
sort-order |
0=Recent, 1=Views, 2=Likes |
1 |
keywords |
Search term | Boeing 747 |
keywords-type |
all, aircraft, registration, photographer |
aircraft |
keywords-contain |
0=exact, 1=starts, 2=ends, 3=contains |
3 |
aircraft |
Aircraft model filter | Airbus A320 |
airline |
Airline filter | Delta Air Lines |
country |
Location filter | United States |
year |
Photo year | 2024 |
photographer |
Photographer filter | John Doe |
width |
Min width (px) | 1920 |
height |
Min height (px) | 1080 |
{
"photos": [
{
"photoId": "12345678",
"registration": "N787BK",
"aircraftType": "Boeing 787-8 Dreamliner",
"airline": "United Airlines",
"photographer": "John Smith",
"location": "Los Angeles International Airport",
"imageUrl": "https://cdn.jetphotos.com/full/6/12345678.jpg",
"likes": "142",
"views": "5847"
}
],
"count": 1,
"meta": {
"free": true,
"mode": "workerd-direct",
"provider": "local-workerd",
"cached": false
}
}CORS is open (Access-Control-Allow-Origin: *).
npm install
npm startKeep it running with one of:
# foreground
npm start
# or pm2
npm install -g pm2
pm2 start npm --name jetphotos-api -- start
pm2 save# terminal 1
npm start
# terminal 2
cloudflared tunnel --url http://127.0.0.1:8787Use the https://...trycloudflare.com URL as your public API.
Your only cost is the VPS itself (can be $0 on free tiers). No scraper SaaS needed.
# on the VPS
sudo apt update
sudo apt install -y git nodejs npm
git clone https://github.com/roowus/Jetphotos-API.git
cd Jetphotos-API
npm install
# bind on all interfaces, port 8787
npm startTest from the VPS:
curl -s "http://127.0.0.1:8787/?page=1&keywords=N787BA&keywords-type=registration&keywords-contain=0" | jq '.count,.error'If you get Cloudflare 403 on the VPS:
- that VPS IP is blocked
- try another region/provider, or run at home instead
server {
listen 80;
server_name jetphotos-api.example.com;
location / {
proxy_pass http://127.0.0.1:8787;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}sudo certbot --nginx -d jetphotos-api.example.comsudo npm i -g pm2
pm2 start npm --name jetphotos-api -- start
pm2 save
pm2 startupThis deploys the same Worker to *.workers.dev.
npm run deployThen:
curl -s "https://jetphotos-api.<your-subdomain>.workers.dev/health"
curl -s "https://jetphotos-api.<your-subdomain>.workers.dev/?page=1&keywords=Boeing%20747&keywords-type=aircraft&keywords-contain=3"Expect possible 403 — JetPhotos frequently blocks Cloudflare edge IPs.
If that happens, use local or VPS hosting instead. The code is the same; only the network path changes.
There is no free reliable way to scrape JetPhotos from Workers edge alone.
- Run this API on a home machine or VPS where
npm startreturns photos - Put a reverse proxy or tunnel in front for HTTPS
- Point your app at that URL
- Skip edge deploy unless you confirm it works for your account/region
| Name | Default | Where | Meaning |
|---|---|---|---|
CACHE_TTL_SECONDS |
21600 |
wrangler.toml [vars] |
Cache successful JSON for 6h |
worker.js # API + JetPhotos parser (free workerd runtime)
wrangler.toml # local + deploy config
package.json # npm start / deploy scripts
scripts/smoke-test.mjs
Cloudflare blocked the current host IP.
- Run locally at home (
npm start) - Or move VPS region/provider
- Edge
workers.devdeploy is the least likely to work
Expected. Keep using self-host (local/VPS/tunnel). Don’t rely on edge deploy for JetPhotos.
Search may be empty, or JetPhotos HTML markup changed. Check meta.targetUrl in a browser.
- Unofficial and not affiliated with JetPhotos
- Be polite: caching is enabled; avoid tight request loops
- Review JetPhotos terms before heavy/production use
MIT